diff --git a/crates/ptex-filter/cpp/ptex_filter_wrapper.cpp b/crates/ptex-filter/cpp/ptex_filter_wrapper.cpp index c081940..b2a119c 100644 --- a/crates/ptex-filter/cpp/ptex_filter_wrapper.cpp +++ b/crates/ptex-filter/cpp/ptex_filter_wrapper.cpp @@ -39,4 +39,27 @@ void ptex_filter_release(PtexFilterHandle filter) { } } +PtexTextureHandle ptex_texture_open(const char* filename, char** error_str) { + Ptex::String error; + Ptex::PtexTexture* tex = Ptex::PtexTexture::open(filename, error); + + if (!tex && error_str) { + *error_str = strdup(error.c_str()); + } + + return tex; +} + +void ptex_texture_release(PtexTextureHandle texture) { + Ptex::PtexTexture* tex = static_cast(texture); + if (tex) { + tex->release(); + } +} + +int32_t ptex_texture_num_channels(PtexTextureHandle texture) { + Ptex::PtexTexture* tex = static_cast(texture); + return tex ? tex->numChannels() : 0; +} + } diff --git a/crates/ptex-filter/cpp/ptex_filter_wrapper.h b/crates/ptex-filter/cpp/ptex_filter_wrapper.h index cd565d4..b999325 100644 --- a/crates/ptex-filter/cpp/ptex_filter_wrapper.h +++ b/crates/ptex-filter/cpp/ptex_filter_wrapper.h @@ -27,8 +27,12 @@ typedef struct { int32_t noedgeblend; } PtexFilterOptions; +PtexTextureHandle ptex_texture_open(const char* filename, char** error_str); +void ptex_filter_release(PtexFilterHandle filter); +int32_t ptex_texture_num_channels(PtexTextureHandle texture); PtexFilterHandle ptex_filter_create(PtexTextureHandle texture, const PtexFilterOptions* opts); + void ptex_filter_eval( PtexFilterHandle filter, float* result, @@ -39,9 +43,9 @@ void ptex_filter_eval( float dudx, float dvdx, float dudy, float dvdy ); - void ptex_filter_release(PtexFilterHandle filter); + #ifdef __cplusplus } #endif diff --git a/crates/ptex-filter/src/ffi.rs b/crates/ptex-filter/src/ffi.rs index 9cc39c7..be7b391 100644 --- a/crates/ptex-filter/src/ffi.rs +++ b/crates/ptex-filter/src/ffi.rs @@ -51,4 +51,10 @@ extern "C" { ); pub fn ptex_filter_release(filter: *mut c_void); + pub fn ptex_texture_open( + filename: *const std::ffi::c_char, + error_str: *mut *mut std::ffi::c_char, + ) -> *mut c_void; + pub fn ptex_texture_release(texture: *mut c_void); + pub fn ptex_texture_num_channels(texture: *mut c_void) -> i32; } diff --git a/crates/ptex-filter/src/lib.rs b/crates/ptex-filter/src/lib.rs index b05041c..35b0d00 100644 --- a/crates/ptex-filter/src/lib.rs +++ b/crates/ptex-filter/src/lib.rs @@ -12,6 +12,9 @@ pub struct PtexFilter { } impl PtexFilter { + /// Creates a new Ptex filter pointer + /// + /// # Safety pub unsafe fn new(texture_ptr: *mut c_void, opts: &PtexFilterOptions) -> Option { let handle = ffi::ptex_filter_create(texture_ptr, opts); NonNull::new(handle).map(|h| Self { diff --git a/shared/src/bxdfs/complex.rs b/shared/src/bxdfs/complex.rs index af90c05..13328f8 100644 --- a/shared/src/bxdfs/complex.rs +++ b/shared/src/bxdfs/complex.rs @@ -10,7 +10,7 @@ use crate::core::scattering::{ TrowbridgeReitzDistribution, fr_complex_from_spectrum, fr_dielectric, fresnel_moment1, reflect, refract, }; -use crate::spectra::{RGBUnboundedSpectrum, SampledSpectrum, StandardColorSpaces}; +use crate::spectra::{DeviceStandardColorSpaces, RGBUnboundedSpectrum, SampledSpectrum}; use crate::utils::math::{ clamp, fast_exp, i0, lerp, log_i0, radians, safe_acos, safe_asin, safe_sqrt, sample_discrete, square, trimmed_logistic, @@ -34,7 +34,7 @@ pub struct HairBxDF { pub s: Float, pub sin_2k_alpha: [Float; P_MAX], pub cos_2k_alpha: [Float; P_MAX], - pub colorspaces: StandardColorSpaces, + pub colorspaces: DeviceStandardColorSpaces, } impl HairBxDF { @@ -45,7 +45,7 @@ impl HairBxDF { beta_m: Float, beta_n: Float, alpha: Float, - colorspaces: StandardColorSpaces, + colorspaces: DeviceStandardColorSpaces, ) -> Self { let mut sin_2k_alpha = [0.; P_MAX]; let mut cos_2k_alpha = [0.; P_MAX]; @@ -141,7 +141,7 @@ impl HairBxDF { pub fn sigma_a_from_concentration( ce: Float, cp: Float, - stdcs: StandardColorSpaces, + stdcs: DeviceStandardColorSpaces, ) -> RGBUnboundedSpectrum { let eumelanin_sigma_a = RGB::new(0.419, 0.697, 1.37); let pheomelanin_sigma_a = RGB::new(0.187, 0.4, 1.05); diff --git a/shared/src/bxdfs/layered.rs b/shared/src/bxdfs/layered.rs index 48de890..db2d6f7 100644 --- a/shared/src/bxdfs/layered.rs +++ b/shared/src/bxdfs/layered.rs @@ -16,8 +16,8 @@ use crate::core::scattering::{ refract, }; use crate::spectra::{ - N_SPECTRUM_SAMPLES, RGBColorSpace, RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, - StandardColorSpaces, + DeviceStandardColorSpaces, N_SPECTRUM_SAMPLES, RGBColorSpace, RGBUnboundedSpectrum, + SampledSpectrum, SampledWavelengths, }; use crate::utils::hash::hash_buffer; use crate::utils::math::{ diff --git a/shared/src/core/color.rs b/shared/src/core/color.rs index eeb3776..2bd3f1b 100644 --- a/shared/src/core/color.rs +++ b/shared/src/core/color.rs @@ -7,6 +7,7 @@ use std::ops::{ use crate::Float; use crate::core::geometry::Point2f; use crate::core::spectrum::Spectrum; +use crate::utils::Ptr; use crate::utils::find_interval; use crate::utils::math::{SquareMatrix, SquareMatrix3f, clamp, evaluate_polynomial, lerp}; @@ -1065,8 +1066,9 @@ impl Mul for Coeffs { #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct RGBToSpectrumTable { - pub z_nodes: *const Float, - pub coeffs: *const Coeffs, + pub z_nodes: Ptr, + pub coeffs: Ptr, + pub n_nodes: u32, } unsafe impl Send for RGBToSpectrumTable {} @@ -1112,7 +1114,8 @@ impl RGBToSpectrumTable { let x = coord_a / z; let y = coord_b / z; - let z_nodes_slice = unsafe { core::slice::from_raw_parts(self.z_nodes, RES as usize) }; + let z_nodes_slice = + unsafe { core::slice::from_raw_parts(self.z_nodes.as_raw(), RES as usize) }; let zi = find_interval(RES, |i| z_nodes_slice[i as usize] < z) as usize; let dz = (z - z_nodes_slice[zi]) / (z_nodes_slice[zi + 1] - z_nodes_slice[zi]); let x_float = x * (RES - 1) as Float; diff --git a/shared/src/core/image.rs b/shared/src/core/image.rs index 49954cb..3f60c9d 100644 --- a/shared/src/core/image.rs +++ b/shared/src/core/image.rs @@ -1,6 +1,7 @@ +use crate::Float; use crate::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR}; use crate::core::geometry::{Bounds2f, Point2f, Point2fi, Point2i}; -use crate::core::pbrt::Float; +use crate::utils::Ptr; use crate::utils::containers::Array2D; use crate::utils::math::{f16_to_f32, lerp, square}; use core::hash; @@ -70,9 +71,9 @@ impl PixelFormat { #[repr(C)] #[derive(Clone, Copy, Debug)] pub enum Pixels { - U8(*const u8), - F16(*const u16), - F32(*const f32), + U8(Ptr), + F16(Ptr), + F32(Ptr), } #[repr(C)] diff --git a/shared/src/core/interaction.rs b/shared/src/core/interaction.rs index 84007f0..7ac3288 100644 --- a/shared/src/core/interaction.rs +++ b/shared/src/core/interaction.rs @@ -220,18 +220,18 @@ pub struct ShadingGeom { #[repr(C)] #[derive(Debug, Default, Clone, Copy)] pub struct SurfaceInteraction { + pub area_light: Ptr, + pub material: Ptr, + pub shape: Ptr, pub common: InteractionBase, + pub shading: ShadingGeom, pub dpdu: Vector3f, pub dpdv: Vector3f, pub dndu: Normal3f, pub dndv: Normal3f, - pub shading: ShadingGeom, - pub face_index: u32, - pub area_light: Ptr, - pub material: Ptr, - pub shape: Ptr, pub dpdx: Vector3f, pub dpdy: Vector3f, + pub face_index: i32, pub dudx: Float, pub dvdx: Float, pub dudy: Float, @@ -609,7 +609,7 @@ impl SurfaceInteraction { dndv: Normal3f, time: Float, flip: bool, - face_index: u32, + face_index: i32, ) -> Self { let mut si = Self::new(pi, uv, wo, dpdu, dpdv, dndu, dndv, time, flip); si.face_index = face_index; diff --git a/shared/src/core/material.rs b/shared/src/core/material.rs index e59263e..3d47319 100644 --- a/shared/src/core/material.rs +++ b/shared/src/core/material.rs @@ -65,7 +65,7 @@ pub struct NormalBumpEvalContext { pub dudy: Float, pub dvdx: Float, pub dvdy: Float, - pub face_index: u32, + pub face_index: i32, } impl From<&SurfaceInteraction> for NormalBumpEvalContext { diff --git a/shared/src/core/sampler.rs b/shared/src/core/sampler.rs index 80e3d25..2127dba 100644 --- a/shared/src/core/sampler.rs +++ b/shared/src/core/sampler.rs @@ -5,7 +5,7 @@ use crate::core::pbrt::{Float, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4}; use crate::utils::Ptr; use crate::utils::containers::Array2D; use crate::utils::math::{ - BinaryPermuteScrambler, DigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler, + BinaryPermuteScrambler, DeviceDigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler, PRIME_TABLE_SIZE, Scrambler, clamp, encode_morton_2, inverse_radical_inverse, lerp, log2_int, owen_scrambled_radical_inverse, permutation_element, radical_inverse, round_up_pow2, scrambled_radical_inverse, sobol_interval_to_index, sobol_sample, @@ -42,13 +42,13 @@ where #[repr(C)] #[derive(Default, Debug, Clone, Copy)] pub struct IndependentSampler { - pub samples_per_pixel: u32, + pub samples_per_pixel: i32, pub seed: u64, pub rng: Rng, } impl IndependentSampler { - pub fn new(samples_per_pixel: u32, seed: u64) -> Self { + pub fn new(samples_per_pixel: i32, seed: u64) -> Self { Self { samples_per_pixel, seed, @@ -58,10 +58,10 @@ impl IndependentSampler { } impl SamplerTrait for IndependentSampler { - fn samples_per_pixel(&self) -> u32 { + fn samples_per_pixel(&self) -> i32 { self.samples_per_pixel } - fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option) { + fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option) { let hash_input = [p.x() as u64, p.y() as u64, self.seed]; let sequence_index = hash_buffer(&hash_input, 0); self.rng.set_sequence(sequence_index); @@ -95,65 +95,18 @@ pub enum RandomizeStrategy { #[repr(C)] #[derive(Default, Debug, Clone, Copy)] pub struct HaltonSampler { - samples_per_pixel: u32, - randomize: RandomizeStrategy, - base_scales: [u64; 2], - base_exponents: [u64; 2], - mult_inverse: [u64; 2], - halton_index: u64, - dim: u32, - digit_permutations: Ptr, + pub samples_per_pixel: i32, + pub randomize: RandomizeStrategy, + pub base_scales: [u64; 2], + pub base_exponents: [u64; 2], + pub mult_inverse: [u64; 2], + pub halton_index: u64, + pub dim: u32, + pub digit_permutations: Ptr, } impl HaltonSampler { - // pub fn new( - // samples_per_pixel: u32, - // full_res: Point2i, - // randomize: RandomizeStrategy, - // seed: u64, - // ) -> Self { - // let digit_permutations = compute_radical_inverse_permutations(seed); - // let mut base_scales = [0u64; 2]; - // let mut base_exponents = [0u64; 2]; - // let bases = [2, 3]; - // let res_coords = [full_res.x(), full_res.y()]; - // - // for i in 0..2 { - // let base = bases[i] as u64; - // let mut scale = 1u64; - // let mut exp = 0u64; - // - // let limit = std::cmp::min(res_coords[i], MAX_HALTON_RESOLUTION) as u64; - // - // while scale < limit { - // scale *= base; - // exp += 1; - // } - // - // base_scales[i] = scale; - // base_exponents[i] = exp; - // } - // - // let mut mult_inverse = [0u64; 2]; - // - // mult_inverse[0] = - // Self::multiplicative_inverse(base_scales[0] as i64, base_scales[0] as i64); - // mult_inverse[1] = - // Self::multiplicative_inverse(base_scales[1] as i64, base_scales[1] as i64); - // - // Self { - // samples_per_pixel, - // randomize, - // digit_permutations, - // base_scales, - // base_exponents, - // mult_inverse, - // halton_index: 0, - // dim: 0, - // } - // } - - fn sample_dimension(&self, dimension: u32) -> Float { + pub fn sample_dimension(&self, dimension: u32) -> Float { if self.randomize == RandomizeStrategy::None { radical_inverse(dimension, self.halton_index) } else if self.randomize == RandomizeStrategy::PermuteDigits { @@ -168,12 +121,12 @@ impl HaltonSampler { } } - fn multiplicative_inverse(a: i64, n: i64) -> u64 { + pub fn multiplicative_inverse(a: i64, n: i64) -> u64 { let (x, _) = Self::extended_gcd(a as u64, n as u64); x.rem_euclid(n) as u64 } - fn extended_gcd(a: u64, b: u64) -> (i64, i64) { + pub fn extended_gcd(a: u64, b: u64) -> (i64, i64) { if b == 0 { return (1, 0); } @@ -187,11 +140,11 @@ impl HaltonSampler { } impl SamplerTrait for HaltonSampler { - fn samples_per_pixel(&self) -> u32 { + fn samples_per_pixel(&self) -> i32 { self.samples_per_pixel } - fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option) { + fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option) { self.halton_index = 0; let sample_stride = self.base_scales[0] * self.base_scales[1]; @@ -252,20 +205,20 @@ impl SamplerTrait for HaltonSampler { #[repr(C)] #[derive(Default, Debug, Clone, Copy)] pub struct StratifiedSampler { - x_pixel_samples: u32, - y_pixel_samples: u32, + x_pixel_samples: i32, + y_pixel_samples: i32, jitter: bool, seed: u64, rng: Rng, pixel: Point2i, - sample_index: u32, + sample_index: i32, dim: u32, } impl StratifiedSampler { pub fn new( - x_pixel_samples: u32, - y_pixel_samples: u32, + x_pixel_samples: i32, + y_pixel_samples: i32, seed: Option, jitter: bool, ) -> Self { @@ -283,11 +236,11 @@ impl StratifiedSampler { } impl SamplerTrait for StratifiedSampler { - fn samples_per_pixel(&self) -> u32 { + fn samples_per_pixel(&self) -> i32 { self.x_pixel_samples * self.y_pixel_samples } - fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option) { + fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option) { self.pixel = p; self.sample_index = sample_index; let hash_input = [p.x() as u64, p.y() as u64, self.seed]; @@ -360,16 +313,16 @@ impl SamplerTrait for StratifiedSampler { #[repr(C)] #[derive(Default, Debug, Clone, Copy)] pub struct PaddedSobolSampler { - samples_per_pixel: u32, + samples_per_pixel: i32, seed: u64, randomize: RandomizeStrategy, pixel: Point2i, - sample_index: u32, + sample_index: i32, dim: u32, } impl PaddedSobolSampler { - pub fn new(samples_per_pixel: u32, randomize: RandomizeStrategy, seed: Option) -> Self { + pub fn new(samples_per_pixel: i32, randomize: RandomizeStrategy, seed: Option) -> Self { Self { samples_per_pixel, seed: seed.unwrap_or(0), @@ -398,10 +351,10 @@ impl PaddedSobolSampler { } impl SamplerTrait for PaddedSobolSampler { - fn samples_per_pixel(&self) -> u32 { + fn samples_per_pixel(&self) -> i32 { self.samples_per_pixel } - fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option) { + fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option) { self.pixel = p; self.sample_index = sample_index; self.dim = dim.unwrap_or(0); @@ -449,7 +402,7 @@ impl SamplerTrait for PaddedSobolSampler { #[derive(Default, Debug, Clone)] pub struct SobolSampler { - samples_per_pixel: u32, + samples_per_pixel: i32, scale: i32, seed: u64, randomize: RandomizeStrategy, @@ -460,7 +413,7 @@ pub struct SobolSampler { impl SobolSampler { pub fn new( - samples_per_pixel: u32, + samples_per_pixel: i32, full_resolution: Point2i, randomize: RandomizeStrategy, seed: Option, @@ -501,10 +454,10 @@ impl SobolSampler { } impl SamplerTrait for SobolSampler { - fn samples_per_pixel(&self) -> u32 { + fn samples_per_pixel(&self) -> i32 { self.samples_per_pixel } - fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option) { + fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option) { self.pixel = p; self.dim = 2.max(dim.unwrap_or(0)); self.sobol_index = @@ -557,7 +510,7 @@ impl SamplerTrait for SobolSampler { pub struct ZSobolSampler { randomize: RandomizeStrategy, seed: u64, - log2_samples_per_pixel: u32, + log2_samples_per_pixel: i32, n_base4_digits: u32, morton_index: u64, dim: u32, @@ -565,7 +518,7 @@ pub struct ZSobolSampler { impl ZSobolSampler { pub fn new( - samples_per_pixel: u32, + samples_per_pixel: i32, full_resolution: Point2i, randomize: RandomizeStrategy, seed: Option, @@ -573,11 +526,11 @@ impl ZSobolSampler { let log2_samples_per_pixel = log2_int(samples_per_pixel as Float) as u32; let res = round_up_pow2(full_resolution.x().max(full_resolution.y())); let log4_samples_per_pixel = log2_samples_per_pixel.div_ceil(2); - let n_base4_digits = log2_int(res as Float) as u32 + log4_samples_per_pixel; + let n_base4_digits = log2_int(res as Float) as u32 + log4_samples_per_pixel as u32; Self { randomize, seed: seed.unwrap_or(0), - log2_samples_per_pixel, + log2_samples_per_pixel: log2_samples_per_pixel as i32, n_base4_digits, morton_index: 0, dim: 0, @@ -641,10 +594,10 @@ impl ZSobolSampler { } impl SamplerTrait for ZSobolSampler { - fn samples_per_pixel(&self) -> u32 { + fn samples_per_pixel(&self) -> i32 { todo!() } - fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option) { + fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option) { self.dim = dim.unwrap_or(0); self.morton_index = (encode_morton_2(p.x() as u32, p.y() as u32) << self.log2_samples_per_pixel) @@ -710,10 +663,10 @@ impl SamplerTrait for ZSobolSampler { #[derive(Default, Debug, Clone)] pub struct MLTSampler; impl SamplerTrait for MLTSampler { - fn samples_per_pixel(&self) -> u32 { + fn samples_per_pixel(&self) -> i32 { todo!() } - fn start_pixel_sample(&mut self, _p: Point2i, _sample_index: u32, _dim: Option) { + fn start_pixel_sample(&mut self, _p: Point2i, _sample_index: i32, _dim: Option) { todo!() } fn get1d(&mut self) -> Float { @@ -729,8 +682,8 @@ impl SamplerTrait for MLTSampler { #[enum_dispatch] pub trait SamplerTrait { - fn samples_per_pixel(&self) -> u32; - fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option); + fn samples_per_pixel(&self) -> i32; + fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option); fn get1d(&mut self) -> Float; fn get2d(&mut self) -> Point2f; fn get_pixel2d(&mut self) -> Point2f; diff --git a/shared/src/core/texture.rs b/shared/src/core/texture.rs index 38db344..6b4450d 100644 --- a/shared/src/core/texture.rs +++ b/shared/src/core/texture.rs @@ -272,7 +272,7 @@ pub struct TextureEvalContext { pub dudy: Float, pub dvdx: Float, pub dvdy: Float, - pub face_index: u32, + pub face_index: i32, } impl TextureEvalContext { @@ -287,7 +287,7 @@ impl TextureEvalContext { dudy: Float, dvdx: Float, dvdy: Float, - face_index: u32, + face_index: i32, ) -> Self { Self { p, diff --git a/shared/src/lights/diffuse.rs b/shared/src/lights/diffuse.rs index 6f9d22a..0341432 100644 --- a/shared/src/lights/diffuse.rs +++ b/shared/src/lights/diffuse.rs @@ -48,7 +48,7 @@ impl DiffuseAreaLight { return false; }; let ctx = TextureEvalContext::from(intr); - let a = UniversalTextureEvaluator.evaluate_float(&*self.alpha, &ctx); + let a = UniversalTextureEvaluator.evaluate_float(&self.alpha, &ctx); if a >= 1.0 { return false; } diff --git a/shared/src/lights/infinite.rs b/shared/src/lights/infinite.rs index 4ce555a..c66faf3 100644 --- a/shared/src/lights/infinite.rs +++ b/shared/src/lights/infinite.rs @@ -15,8 +15,8 @@ use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths use crate::spectra::{RGBColorSpace, RGBIlluminantSpectrum}; use crate::utils::math::{clamp, equal_area_sphere_to_square, equal_area_square_to_sphere, square}; use crate::utils::sampling::{ - AliasTable, DevicePiecewiseConstant2D, WindowedPiecewiseConstant2D, sample_uniform_sphere, - uniform_sphere_pdf, + AliasTable, DevicePiecewiseConstant2D, DeviceWindowedPiecewiseConstant2D, + sample_uniform_sphere, uniform_sphere_pdf, }; use crate::utils::{Ptr, Transform}; use crate::{Float, PI}; @@ -132,11 +132,11 @@ impl ImageInfiniteLight { for c in 0..3 { rgb[c] = self.image.lookup_nearest_channel_with_wrap( uv, - c as i32, + c, WrapMode::OctahedralSphere.into(), ); } - let spec = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero()); + let spec = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero()); self.scale * spec.sample(lambda) } } @@ -222,11 +222,11 @@ impl LightTrait for ImageInfiniteLight { for c in 0..3 { rgb[c] = self.image.get_channel_with_wrap( Point2i::new(u, v), - c as i32, + c, WrapMode::OctahedralSphere.into(), ); } - sum_l += RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero()) + sum_l += RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero()) .sample(&lambda); } } @@ -255,7 +255,7 @@ pub struct PortalInfiniteLight { pub scale: Float, pub portal: [Point3f; 4], pub portal_frame: Frame, - pub distribution: WindowedPiecewiseConstant2D, + pub distribution: DeviceWindowedPiecewiseConstant2D, pub scene_center: Point3f, pub scene_radius: Float, } @@ -264,9 +264,9 @@ impl PortalInfiniteLight { pub fn image_lookup(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum { let mut rgb = RGB::default(); for c in 0..3 { - rgb[c] = self.image.lookup_nearest_channel(uv, c as i32) + rgb[c] = self.image.lookup_nearest_channel(uv, c) } - let spec = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero()); + let spec = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero()); self.scale * spec.sample(lambda) } diff --git a/shared/src/materials/complex.rs b/shared/src/materials/complex.rs index 9761cd0..f669d6b 100644 --- a/shared/src/materials/complex.rs +++ b/shared/src/materials/complex.rs @@ -31,6 +31,7 @@ pub struct HairMaterial { impl HairMaterial { #[cfg(not(target_os = "cuda"))] + #[allow(clippy::too_many_arguments)] pub fn new( sigma_a: Ptr, color: Ptr, diff --git a/shared/src/shapes/triangle.rs b/shared/src/shapes/triangle.rs index 8fbd982..6058f96 100644 --- a/shared/src/shapes/triangle.rs +++ b/shared/src/shapes/triangle.rs @@ -35,7 +35,7 @@ impl TriangleIntersection { #[derive(Clone, Copy, Debug)] pub struct TriangleShape { pub mesh: DeviceTriangleMesh, - pub tri_index: u32, + pub tri_index: i32, } impl TriangleShape { @@ -111,7 +111,7 @@ impl TriangleShape { } } - pub fn new(mesh: DeviceTriangleMesh, tri_index: u32) -> Self { + pub fn new(mesh: DeviceTriangleMesh, tri_index: i32) -> Self { Self { mesh, tri_index } } diff --git a/shared/src/spectra/colorspace.rs b/shared/src/spectra/colorspace.rs index bd1e04c..3ddb544 100644 --- a/shared/src/spectra/colorspace.rs +++ b/shared/src/spectra/colorspace.rs @@ -9,14 +9,14 @@ use std::cmp::{Eq, PartialEq}; #[repr(C)] #[derive(Copy, Debug, Clone)] -pub struct StandardColorSpaces { +pub struct DeviceStandardColorSpaces { pub srgb: Ptr, pub dci_p3: Ptr, pub rec2020: Ptr, pub aces2065_1: Ptr, } -impl StandardColorSpaces { +impl DeviceStandardColorSpaces { #[cfg(not(target_arch = "nvptx64"))] pub fn get_named(&self, name: &str) -> Option> { match name.to_lowercase().as_str() { diff --git a/shared/src/spectra/mod.rs b/shared/src/spectra/mod.rs index 3064da3..b59d777 100644 --- a/shared/src/spectra/mod.rs +++ b/shared/src/spectra/mod.rs @@ -6,7 +6,7 @@ pub mod simple; use crate::core::pbrt::Float; -pub use colorspace::{RGBColorSpace, StandardColorSpaces}; +pub use colorspace::{DeviceStandardColorSpaces, RGBColorSpace}; pub use rgb::*; pub use sampled::{CIE_Y_INTEGRAL, LAMBDA_MAX, LAMBDA_MIN}; pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths}; diff --git a/shared/src/textures/image.rs b/shared/src/textures/image.rs index 73770b1..e8ff30d 100644 --- a/shared/src/textures/image.rs +++ b/shared/src/textures/image.rs @@ -6,10 +6,10 @@ use crate::spectra::{ RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, }; +use crate::utils::Ptr; /* GPU heavy code, dont know if this will ever work the way Im doing things. * Leaving it here isolated, for careful handling */ - #[repr(C)] #[derive(Clone, Debug, Copy)] pub struct GPUSpectrumImageTexture { diff --git a/shared/src/textures/mix.rs b/shared/src/textures/mix.rs index 850284e..6a8991c 100644 --- a/shared/src/textures/mix.rs +++ b/shared/src/textures/mix.rs @@ -14,15 +14,15 @@ pub struct GPUFloatMixTexture { impl GPUFloatMixTexture { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { - let amt = self.amount.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0); + let amt = self.amount.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0); let t1 = if amt != 1.0 { - self.tex1.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0) + self.tex1.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0) } else { 0.0 }; let t2 = if amt != 0.0 { - self.tex2.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0) + self.tex2.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0) } else { 0.0 }; @@ -43,13 +43,13 @@ impl GPUFloatDirectionMixTexture { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { let amt = self.dir.abs_dot(ctx.n.into()); let t1 = if amt != 1.0 { - self.tex1.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0) + self.tex1.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0) } else { 0.0 }; let t2 = if amt != 0.0 { - self.tex2.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0) + self.tex2.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0) } else { 0.0 }; @@ -72,11 +72,11 @@ impl GPUSpectrumMixTexture { ctx: &TextureEvalContext, lambda: &SampledWavelengths, ) -> SampledSpectrum { - let amt = self.amount.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0); + let amt = self.amount.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0); let t1 = if amt != 1.0 { self.tex1 .get() - .map(|t| t.evaluate(&ctx, &lambda)) + .map(|t| t.evaluate(ctx, lambda)) .unwrap_or(SampledSpectrum::new(0.)) } else { SampledSpectrum::new(0.) @@ -85,7 +85,7 @@ impl GPUSpectrumMixTexture { let t2 = if amt != 0.0 { self.tex2 .get() - .map(|t| t.evaluate(&ctx, &lambda)) + .map(|t| t.evaluate(ctx, lambda)) .unwrap_or(SampledSpectrum::new(0.)) } else { SampledSpectrum::new(0.) @@ -113,7 +113,7 @@ impl GPUSpectrumDirectionMixTexture { let t1 = if amt != 1.0 { self.tex1 .get() - .map(|t| t.evaluate(&ctx, &lambda)) + .map(|t| t.evaluate(ctx, lambda)) .unwrap_or(SampledSpectrum::new(0.)) } else { SampledSpectrum::new(0.) @@ -122,7 +122,7 @@ impl GPUSpectrumDirectionMixTexture { let t2 = if amt != 0.0 { self.tex2 .get() - .map(|t| t.evaluate(&ctx, &lambda)) + .map(|t| t.evaluate(ctx, lambda)) .unwrap_or(SampledSpectrum::new(0.)) } else { SampledSpectrum::new(0.) diff --git a/shared/src/textures/ptex.rs b/shared/src/textures/ptex.rs index e85f68a..b5ed2bc 100644 --- a/shared/src/textures/ptex.rs +++ b/shared/src/textures/ptex.rs @@ -1,10 +1,10 @@ use crate::Float; -use crate::core::color::RGB; +use crate::core::color::{ColorEncoding, RGB}; use crate::core::spectrum::{SpectrumTrait, StandardSpectra}; use crate::core::texture::{SpectrumType, TextureEvalContext}; use crate::spectra::{ - RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, - SampledWavelengths, StandardColorSpaces, + DeviceStandardColorSpaces, RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, + RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, }; use crate::utils::Ptr; @@ -23,10 +23,10 @@ impl GPUFloatPtexTexture { #[repr(C)] #[derive(Clone, Debug, Copy)] pub struct GPUSpectrumPtexTexture { - pub face_values: *const RGB, + pub face_values: Ptr, pub n_faces: u32, pub spectrum_type: SpectrumType, - pub colorspaces: StandardColorSpaces, + pub colorspaces: DeviceStandardColorSpaces, } impl GPUSpectrumPtexTexture { @@ -35,7 +35,7 @@ impl GPUSpectrumPtexTexture { ctx: &TextureEvalContext, lambda: &SampledWavelengths, ) -> SampledSpectrum { - let index = ctx.face_index.clamp(0, self.n_faces.saturating_sub(1)); + let index = (ctx.face_index as u32).clamp(0, self.n_faces.saturating_sub(1)); let rgb = unsafe { &*self.face_values.add(index as usize) }; let s_rgb = self.colorspaces.srgb; diff --git a/shared/src/utils/math.rs b/shared/src/utils/math.rs index a3d45c9..18cbcfc 100644 --- a/shared/src/utils/math.rs +++ b/shared/src/utils/math.rs @@ -750,13 +750,13 @@ pub fn inverse_radical_inverse(mut inverse: u64, base: u64, n_digits: u64) -> u6 // Digit scrambling #[repr(C)] #[derive(Default, Debug, Copy, Clone)] -pub struct DigitPermutation { - pub base: u32, +pub struct DeviceDigitPermutation { + pub base: i32, pub n_digits: u32, pub permutations: Ptr, } -impl DigitPermutation { +impl DeviceDigitPermutation { #[inline(always)] pub fn permute(&self, digit_index: i32, digit_value: i32) -> i32 { let idx = (digit_index * self.base as i32 + digit_value) as usize; @@ -765,7 +765,11 @@ impl DigitPermutation { } } -pub fn scrambled_radical_inverse(base_index: u32, mut a: u64, perm: &DigitPermutation) -> Float { +pub fn scrambled_radical_inverse( + base_index: u32, + mut a: u64, + perm: &DeviceDigitPermutation, +) -> Float { let base = PRIMES[base_index as usize] as u64; let limit = (u64::MAX / base).saturating_sub(base); diff --git a/shared/src/utils/mesh.rs b/shared/src/utils/mesh.rs index 4f76c34..e091c93 100644 --- a/shared/src/utils/mesh.rs +++ b/shared/src/utils/mesh.rs @@ -7,14 +7,14 @@ use crate::utils::sampling::DevicePiecewiseConstant2D; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct DeviceTriangleMesh { - pub n_triangles: u32, - pub n_vertices: u32, - pub vertex_indices: Ptr, pub p: Ptr, pub n: Ptr, pub s: Ptr, pub uv: Ptr, - pub face_indices: Ptr, + pub vertex_indices: Ptr, + pub face_indices: Ptr, + pub n_triangles: u32, + pub n_vertices: u32, pub reverse_orientation: bool, pub transform_swaps_handedness: bool, } @@ -22,15 +22,15 @@ pub struct DeviceTriangleMesh { #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct DeviceBilinearPatchMesh { - pub n_patches: u32, - pub n_vertices: u32, - pub vertex_indices: Ptr, + pub image_distribution: Ptr, pub p: Ptr, pub n: Ptr, pub uv: Ptr, + pub vertex_indices: Ptr, + pub n_patches: u32, + pub n_vertices: u32, pub reverse_orientation: bool, pub transform_swaps_handedness: bool, - pub image_distribution: Ptr, } unsafe impl Send for DeviceTriangleMesh {} diff --git a/shared/src/utils/sampling.rs b/shared/src/utils/sampling.rs index 91c502d..ff122e5 100644 --- a/shared/src/utils/sampling.rs +++ b/shared/src/utils/sampling.rs @@ -772,12 +772,15 @@ impl DevicePiecewiseConstant1D { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct DevicePiecewiseConstant2D { - pub conditionals: *const DevicePiecewiseConstant1D, // Array of n_v conditionals + pub conditionals: Ptr, // Array of n_v conditionals pub marginal: DevicePiecewiseConstant1D, pub n_u: u32, pub n_v: u32, } +unsafe impl Send for DevicePiecewiseConstant2D {} +unsafe impl Sync for DevicePiecewiseConstant2D {} + impl DevicePiecewiseConstant2D { // pub fn resolution(&self) -> Point2i { // Point2i::new( @@ -792,7 +795,7 @@ impl DevicePiecewiseConstant2D { pub fn sample(&self, u: Point2f) -> (Point2f, f32, Point2i) { let (d1, pdf1, off_y) = self.marginal.sample(u.y()); - let (d0, pdf0, off_x) = (unsafe { &*self.conditionals.add(off_y) }).sample(u.x()); + let (d0, pdf0, off_x) = (unsafe { self.conditionals.add(off_y) }).sample(u.x()); let pdf = pdf0 * pdf1; let offset = Point2i::new(off_x as i32, off_y as i32); (Point2f::new(d0, d1), pdf, offset) @@ -817,11 +820,11 @@ impl DevicePiecewiseConstant2D { #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct SummedAreaTable { +pub struct DeviceSummedAreaTable { pub sum: Array2D, } -impl SummedAreaTable { +impl DeviceSummedAreaTable { // pub fn new(values: &Array2D) -> Self { // let width = values.x_size(); // let height = values.y_size(); @@ -897,17 +900,12 @@ impl SummedAreaTable { #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct WindowedPiecewiseConstant2D { - sat: SummedAreaTable, - func: Array2D, +pub struct DeviceWindowedPiecewiseConstant2D { + pub sat: DeviceSummedAreaTable, + pub func: Array2D, } -impl WindowedPiecewiseConstant2D { - // pub fn new(func: Array2D) -> Self { - // let sat = SummedAreaTable::new(&func); - // Self { sat, func } - // } - +impl DeviceWindowedPiecewiseConstant2D { pub fn sample(&self, u: Point2f, b: Bounds2f) -> Option<(Point2f, Float)> { let b_int = self.sat.integral(b); if b_int == 0.0 { diff --git a/src/core/camera.rs b/src/core/camera.rs index d3ff02a..776fb17 100644 --- a/src/core/camera.rs +++ b/src/core/camera.rs @@ -16,7 +16,7 @@ use std::path::Path; use std::sync::Arc; #[repr(C)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] pub struct CameraBaseParameters { pub camera_transform: CameraTransform, pub shutter_open: Float, diff --git a/src/core/color.rs b/src/core/color.rs index e2ada25..470af75 100644 --- a/src/core/color.rs +++ b/src/core/color.rs @@ -1,4 +1,5 @@ use crate::utils::read_float_file; +use anyhow::Error; use shared::Float; use shared::core::color::{Coeffs, RES, RGBToSpectrumTable}; use std::ops::Deref; @@ -25,7 +26,8 @@ impl RGBToSpectrumTableData { let view = RGBToSpectrumTable { z_nodes: z_nodes.as_ptr(), - coeffs: coeffs.as_ptr() as *const Coeffs, + coeffs: coeffs.as_ptr().into(), + n_nodes: z_nodes.len(), }; Self { diff --git a/src/core/image/io.rs b/src/core/image/io.rs index d824920..2673563 100644 --- a/src/core/image/io.rs +++ b/src/core/image/io.rs @@ -1,6 +1,7 @@ use super::{Image, ImageAndMetadata, ImageMetadata}; use crate::core::image::{PixelStorage, WrapMode}; use crate::utils::error::ImageError; +use anyhow::Error; use anyhow::{Context, Result, bail}; use exr::prelude::{read_first_rgba_layer_from_file, write_rgba_file}; use image_rs::{DynamicImage, ImageReader}; @@ -37,7 +38,7 @@ impl ImageIO for Image { } } - fn write(&self, filename: &str, metadata: &ImageMetadata) -> Result<(), ImageError> { + fn write(&self, filename: &str, metadata: &ImageMetadata) -> Result<(), Error> { let path = Path::new(filename); let ext = path.extension().and_then(|s| s.to_str()).unwrap_or(""); let res = match ext.to_lowercase().as_str() { diff --git a/src/core/image/metadata.rs b/src/core/image/metadata.rs index f87bee0..5d94c93 100644 --- a/src/core/image/metadata.rs +++ b/src/core/image/metadata.rs @@ -48,3 +48,9 @@ pub struct ImageMetadata { pub strings: HashMap, pub string_vectors: HashMap>, } + +impl ImageMetadata { + pub fn get_colorspace(&self) -> Option { + self.colorspace + } +} diff --git a/src/core/image/mod.rs b/src/core/image/mod.rs index da4e2e9..4a2305c 100644 --- a/src/core/image/mod.rs +++ b/src/core/image/mod.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use half::f16; use shared::Float; use shared::core::color::ColorEncoding; @@ -98,12 +99,21 @@ impl PixelStorage { } } +#[derive(Debug, Clone)] pub struct Image { storage: PixelStorage, channel_names: Vec, - device: DeviceImage, + pub device: DeviceImage, } +// impl Deref for Image { +// type Target = DeviceImage; +// #[inline] +// fn deref(&self) -> &Self::Target { +// &self.device +// } +// } + #[derive(Debug, Clone)] pub struct ImageAndMetadata { pub image: Image, @@ -115,7 +125,7 @@ impl Image { fn from_storage( storage: PixelStorage, resolution: Point2i, - channel_names: Vec, + channel_names: &[&str], encoding: ColorEncoding, ) -> Self { let n_channels = channel_names.len() as i32; @@ -142,7 +152,7 @@ impl Image { pub fn from_u8( data: Vec, resolution: Point2i, - channel_names: Vec, + channel_names: &[&str], encoding: ColorEncoding, ) -> Self { Self::from_storage( @@ -156,7 +166,7 @@ impl Image { pub fn from_u8( data: Vec, resolution: Point2i, - channel_names: Vec, + channel_names: &[&str], encoding: ColorEncoding, ) -> Self { Self::from_storage( @@ -167,7 +177,7 @@ impl Image { ) } - pub fn from_f16(data: Vec, resolution: Point2i, channel_names: Vec) -> Self { + pub fn from_f16(data: Vec, resolution: Point2i, channel_names: &[&str]) -> Self { Self::from_storage( PixelStorage::F16(data.into_boxed_slice()), resolution, @@ -176,7 +186,7 @@ impl Image { ) } - pub fn from_f32(data: Vec, resolution: Point2i, channel_names: Vec) -> Self { + pub fn from_f32(data: Vec, resolution: Point2i, channel_names: &[&str]) -> Self { Self::from_storage( PixelStorage::F32(data.into_boxed_slice()), resolution, @@ -189,7 +199,7 @@ impl Image { format: PixelFormat, resolution: Point2i, channel_names: &[&str], - encoding: *const ColorEncoding, + encoding: Arc, ) -> Self { let n_channels = channel_names.len(); let pixel_count = (resolution.x * resolution.y) as usize * n_channels; @@ -204,6 +214,29 @@ impl Image { Self::from_storage(storage, resolution, owned_names, encoding) } + pub fn new_constant(resolution: Point2i, channel_names: &[&str], values: &[f32]) -> Self { + let n_channels = channel_names.len(); + if values.len() != n_channels { + panic!( + "Image::new_constant: values length ({}) must match channel count ({})", + values.len(), + n_channels + ); + } + + let n_pixels = (resolution.x * resolution.y) as usize; + + let mut data = Vec::with_capacity(n_pixels * n_channels); + + for _ in 0..n_pixels { + data.extend_from_slice(values); + } + + let owned_names: Vec = channel_names.iter().map(|s| s.to_string()).collect(); + + Self::from_f32(data, resolution, owned_names) + } + // Access pub fn device_image(&self) -> &DeviceImage { &self.device @@ -365,10 +398,7 @@ impl Image { .collect() } - pub fn get_channel_desc( - &self, - requested_channels: &[&str], - ) -> Result { + pub fn get_channel_desc(&self, requested_channels: &[&str]) -> Result { let mut offset = Vec::with_capacity(requested_channels.len()); for &req in requested_channels.iter() { @@ -562,6 +592,23 @@ impl Image { } return false; } + + pub fn has_any_nan_pixels(&self) -> bool { + if self.format() == PixelFormat::Float { + return false; + } + + for y in 0..self.resolution().y() { + for x in 0..self.resolution().x() { + for c in 0..self.n_channels() { + if self.get_channel(Point2i::new(x, y), c).is_nan() { + return true; + } + } + } + } + return false; + } } impl std::ops::Deref for Image { diff --git a/src/core/sampler.rs b/src/core/sampler.rs index 9eb19d5..a1ee383 100644 --- a/src/core/sampler.rs +++ b/src/core/sampler.rs @@ -1,11 +1,11 @@ use crate::Arena; use crate::utils::{FileLoc, ParameterDictionary}; +use anyhow::{Result, anyhow}; use shared::core::geometry::Point2i; use shared::core::sampler::{ HaltonSampler, IndependentSampler, PaddedSobolSampler, Sampler, SobolSampler, StratifiedSampler, ZSobolSampler, }; -use std::fmt::Error; pub trait CreateSampler { fn create( @@ -13,7 +13,7 @@ pub trait CreateSampler { full_res: Point2i, loc: &FileLoc, arena: &mut Arena, - ) -> Result; + ) -> Result; } pub trait SamplerFactory { @@ -23,7 +23,7 @@ pub trait SamplerFactory { full_res: Point2i, loc: &FileLoc, arena: &mut Arena, - ) -> Result; + ) -> Result; } impl SamplerFactory for Sampler { @@ -33,7 +33,7 @@ impl SamplerFactory for Sampler { full_res: Point2i, loc: &FileLoc, arena: &mut Arena, - ) -> Result { + ) -> Result { match name { "zsobol" => { let sampler = ZSobolSampler::create(params, full_res, loc, arena)?; @@ -59,7 +59,7 @@ impl SamplerFactory for Sampler { let sampler = StratifiedSampler::create(params, full_res, loc, arena)?; Ok(Sampler::Stratified(sampler)) } - _ => Err(format!("Film type '{}' unknown at {}", name, loc)), + _ => Err(anyhow!("Film type '{}' unknown at {}", name, loc)), } } } diff --git a/src/core/scene.rs b/src/core/scene.rs deleted file mode 100644 index 5f69c45..0000000 --- a/src/core/scene.rs +++ /dev/null @@ -1,1571 +0,0 @@ -use crate::core::filter::FilterFactory; -use crate::core::image::{Image, io::ImageIO}; -use crate::core::material::MaterialFactory; -use crate::utils::parallel::{AsyncJob, run_async}; -use crate::utils::parameters::{ - NamedTextures, ParameterDictionary, ParsedParameterVector, TextureParameterDictionary, -}; -use crate::utils::parser::ParserTarget; -use crate::utils::{Arena, Upload}; -use crate::utils::{normalize_utf8, resolve_filename}; -use image_rs::Primitive; -use parking_lot::Mutex; -use shared::Float; -use shared::core::camera::{Camera, CameraTransform}; -use shared::core::color::ColorEncoding; -use shared::textures::*; -// use shared::core::color::LINEAR; -use crate::core::texture::{FloatTexture, SpectrumTexture}; -use crate::utils::error::FileLoc; -use shared::core::film::Film; -use shared::core::filter::Filter; -use shared::core::geometry::Vector3f; -use shared::core::light::Light; -use shared::core::material::Material; -use shared::core::medium::{Medium, MediumInterface}; -use shared::core::options::RenderingCoordinateSystem; -use shared::core::primitive::{GeometricPrimitive, PrimitiveTrait, SimplePrimitive}; -use shared::core::sampler::Sampler; -use shared::core::shape::Shape; -use shared::core::texture::SpectrumType; -use shared::spectra::RGBColorSpace; -use shared::utils::transform::{AnimatedTransform, Transform, look_at}; -use std::collections::{HashMap, HashSet}; -use std::ops::{Index as IndexTrait, IndexMut as IndexMutTrait}; -use std::sync::Arc; -// use std::sync::atomic::{AtomicI32, Ordering}; - -#[derive(Clone, Debug)] -pub enum MaterialRef { - Index(usize), - Name(String), - None, -} - -#[derive(Clone, Default, Debug)] -pub struct SceneEntity { - pub name: String, - pub loc: FileLoc, - pub parameters: ParameterDictionary, -} - -#[derive(Clone, Debug)] -pub struct TransformedSceneEntity { - pub base: SceneEntity, - pub render_from_object: AnimatedTransform, -} - -pub type MediumSceneEntity = TransformedSceneEntity; -pub type TextureSceneEntity = TransformedSceneEntity; - -#[derive(Clone, Debug)] -pub struct CameraSceneEntity { - pub base: SceneEntity, - pub camera_transform: CameraTransform, - pub medium: String, -} - -#[derive(Clone, Debug)] -pub struct ShapeSceneEntity { - pub base: SceneEntity, - pub render_from_object: Arc, - pub object_from_render: Arc, - pub reverse_orientation: bool, - pub material: MaterialRef, - pub light_index: Option, - - pub inside_medium: String, - pub outside_medium: String, -} - -#[derive(Clone, Debug)] -pub struct AnimatedShapeSceneEntity { - pub transformed_base: TransformedSceneEntity, - - pub identity: Arc, - pub reverse_orientation: bool, - pub material: MaterialRef, - pub light_index: Option, - pub inside_medium: String, - pub outside_medium: String, -} - -#[derive(Clone, Debug)] -pub struct InstanceDefinitionSceneEntity { - pub name: String, - pub loc: FileLoc, - pub shapes: Vec, - pub animated_shapes: Vec, -} - -#[derive(Clone, Debug)] -pub struct LightSceneEntity { - pub transformed_base: TransformedSceneEntity, - pub medium: String, -} - -#[derive(Clone, Debug)] -pub enum InstanceTransform { - Animated(AnimatedTransform), - Static(Arc), -} - -#[derive(Clone, Debug)] -pub struct InstanceSceneEntity { - pub name: String, - pub loc: FileLoc, - pub transform: InstanceTransform, -} - -#[derive(Default)] -pub struct TextureState { - pub serial_float_textures: Vec<(String, TextureSceneEntity)>, - pub serial_spectrum_textures: Vec<(String, TextureSceneEntity)>, - pub async_spectrum_textures: Vec<(String, TextureSceneEntity)>, - pub loading_texture_filenames: HashSet, - pub float_texture_jobs: HashMap>>, - pub spectrum_texture_jobs: HashMap>>, - pub n_missing_textures: i32, -} - -#[derive(Default)] -pub struct MaterialState { - pub named_materials: Vec<(String, SceneEntity)>, - pub materials: Vec, - pub normal_map_jobs: HashMap>>, - pub normal_maps: HashMap>, -} - -#[derive(Default)] -pub struct LightState { - pub light_jobs: Vec>, - pub area_lights: Vec, -} - -#[derive(Default)] -pub struct MediaState { - pub jobs: HashMap>, - pub map: HashMap>, -} - -#[derive(Default)] -pub struct SingletonState { - pub result: Option>, - pub job: Option>, -} - -pub struct BasicScene { - pub integrator: Option, - pub accelerator: Option, - pub film_colorspace: Option>, - - pub shapes: Mutex>, - pub animated_shapes: Mutex>, - - pub instances: Mutex>, - pub instance_definitions: Mutex>>, - - pub media_state: Mutex, - pub material_state: Mutex, - pub light_state: Mutex, - pub texture_state: Mutex, - - pub camera_state: Mutex>, - pub sampler_state: Mutex>, - pub film_state: Mutex>, -} - -struct SceneLookup<'a> { - textures: &'a NamedTextures, - media: &'a HashMap, - named_materials: &'a HashMap, - materials: &'a Vec, - shape_lights: &'a HashMap>, -} - -impl<'a> SceneLookup<'a> { - fn find_medium(&self, name: &str, loc: &FileLoc) -> Option { - if name.is_empty() { - return None; - } - self.media.get(name).cloned().or_else(|| { - panic!("{}: medium '{}' not defined", loc, name); - }) - } - - fn resolve_material(&self, name: &str, loc: &FileLoc) -> Material { - if !name.is_empty() { - *self.named_materials.get(name).expect("Material not found") - } // else { - // self.materials[index] - // } - } -} - -impl BasicScene { - fn set_options( - self: &Arc, - filter: SceneEntity, - film: SceneEntity, - camera: CameraSceneEntity, - sampler: SceneEntity, - integ: SceneEntity, - accel: SceneEntity, - ) { - *self.integrator.lock() = Some(integ); - *self.accelerator.lock() = Some(accel); - - if let Some(cs) = film.parameters.color_space.as_ref() { - *self.film_colorspace.lock() = Some(Arc::clone(cs)); - } - - let filt = Filter::create(&filter.name, filter.parameters, &filter.loc); - let shutter_close = camera.base.parameters.get_one_float("shutterclose", 1.); - let shutter_open = camera.base.parameters.get_one_float("shutteropen", 0.); - let exposure_time = shutter_close - shutter_open; - let film_instance = Arc::new( - Film::create( - &film.name, - &film.parameters, - exposure_time, - filt.expect("Must have a filter"), - Some(camera.camera_transform.clone()), - &film.loc, - ) - .expect("Must have a film"), - ); - - *self.film.lock() = Some(Arc::clone(&film_instance)); - let sampler_film = Arc::clone(&film_instance); - let sampler_job = std::thread::spawn(move || { - let res = sampler_film.as_ref().base().full_resolution; - Sampler::create(&sampler.name, &sampler.parameters, res, &sampler.loc) - .expect("Sampler was not correctly created") - }); - *self.sampler_job.lock() = Some(sampler_job); - - let camera_film = Arc::clone(&film_instance); - let scene_ptr = self.clone(); - let camera_job = std::thread::spawn(move || { - let medium = scene_ptr.get_medium(&camera.medium, &camera.base.loc); - Camera::create( - &camera.base.name, - &camera.base.parameters, - medium.unwrap(), - &camera.camera_transform, - camera_film, - &camera.base.loc, - ) - .expect("Failed to create camera") - }); - *self.camera_job.lock() = Some(camera_job); - } - - pub fn add_named_material(&self, name: &str, material: SceneEntity) { - let mut state = self.material_state.lock(); - self.start_loading_normal_maps(&mut state, material.parameters); - state.named_materials.push((name.to_string(), material)); - } - - pub fn add_material(&self, name: &str, material: SceneEntity) -> usize { - let mut state = self.material_state.lock(); - self.start_loading_normal_maps(&mut state, material.parameters); - state.materials.push(material); - state.materials.len() - 1 - } - - pub fn add_float_texture(&self, name: &str, texture: &TextureSceneEntity) { - if texture.render_from_object.is_animated() { - log::info!( - "{}: Animated world to texture not supported, using start", - texture.base.loc - ); - } - - let mut texture_state = self.texture_data.lock(); - if name != "imagemap" && name != "ptex" { - texture_state - .serial_float_textures - .push((name.to_string(), texture.clone())); - return; - } - - let filename = resolve_filename(&texture.base.parameters.get_one_string("filename", "")); - if filename.is_empty() { - log::error!( - "[{:?}] \"string filename\" not provided for image texture.", - texture.base.loc - ); - texture_state.n_missing_textures += 1; - return; - } - if !std::path::Path::new(&filename).exists() { - log::error!("[{:?}] {}: file not found.", texture.base.loc, filename); - texture_state.n_missing_textures += 1; - return; - } - - if texture_state.loading_texture_filenames.contains(&filename) { - texture_state - .serial_float_textures - .push((name.to_string(), texture.clone())); - return; - } - - texture_state - .loading_texture_filenames - .insert(filename.clone()); - - let texture_clone = texture.clone(); - let name_clone = name.to_string(); - - let job = run_async(move || { - let render_from_texture = texture_clone.render_from_object.start_transform(); - let tex_dict = - TextureParameterDictionary::new(&texture_clone.base.parameters.into(), None); - - FloatTexture::create( - &texture_clone.name, - render_from_texture, - tex_dict, - &texture_clone.base.loc, - ) - }); - - texture_state - .float_texture_jobs - .insert(name.to_string(), job); - } - - pub fn add_spectrum_texture(&self, name: &str, texture: &TextureSceneEntity) { - if texture.render_from_object.is_animated() { - log::info!( - "{}: Animated world to texture not supported, using start", - texture.base.loc - ); - } - - let mut texture_state = self.texture_data.lock(); - if name != "imagemap" && name != "ptex" { - texture_state - .serial_spectrum_textures - .push((name.to_string(), texture.clone())); - return; - } - - let filename = resolve_filename(&texture.base.parameters.get_one_string("filename", "")); - if filename.is_empty() { - log::error!( - "[{:?}] \"string filename\" not provided for image texture.", - texture.base.loc - ); - texture_state.n_missing_textures += 1; - return; - } - - if !std::path::Path::new(&filename).exists() { - log::error!("[{:?}] {}: file not found.", texture.base.loc, filename); - texture_state.n_missing_textures += 1; - return; - } - - if texture_state.loading_texture_filenames.contains(&filename) { - texture_state - .serial_float_textures - .push((name.to_string(), texture.clone())); - return; - } - - texture_state - .loading_texture_filenames - .insert(filename.clone()); - - let texture_clone = texture.clone(); - let name_clone = name.to_string(); - - let job = run_async(move || { - let render_from_texture = texture_clone.render_from_object.start_transform(); - let tex_dict = - TextureParameterDictionary::new(&texture_clone.base.parameters.into(), None); - - SpectrumTexture::create( - &texture_clone.name, - render_from_texture, - tex_dict, - SpectrumType::Albedo, - &texture_clone.base.loc, - ) - }); - - texture_state - .float_texture_jobs - .insert(name.to_string(), job); - } - - pub fn add_light(&self, light: LightSceneEntity) { - if light.transformed_base.render_from_object.is_animated() { - log::warn!( - "{}: Animated world to texture not supported, using start", - light.transformed_base.base.loc - ); - } - - let medium = self - .get_medium(light.medium, &light.transformed_base.base.loc) - .unwrap(); - let camera_transform = self.get_camera().camera_transform; - - let light_clone = light.clone(); - let mut light_state = self.light_state.lock().unwrap(); - let job = run_async(move || { - let render_from_light = light_clone - .transformed_base - .render_from_object - .start_transform; - - Light::create( - &light_clone.transformed_base.base.name, - &light_clone.transformed_base.base.parameters, - render_from_light, - camera_transform, - medium, - &light_clone.transformed_base.base.loc, - ) - }); - light_state.light_jobs.insert(job); - } - - pub fn add_area_light(&self, light: SceneEntity) -> usize { - let mut state = self.light_state.lock(); - state.area_lights.push(light.clone()); - state.area_lights.len() - 1 - } - - pub fn add_shapes(&self, new_shapes: &mut Vec) { - let mut shapes = self.shapes.lock().unwrap(); - shapes.append(new_shapes); - } - - pub fn add_animated_shapes(&self, new_shapes: &mut Vec) -> usize { - let mut shapes = self.animated_shapes.lock().unwrap(); - shapes.append(new_shapes); - } - - pub fn add_instance_definition(&self, instance: InstanceDefinitionSceneEntity) { - let def = Arc::new(instance); - let name = def.name.clone(); - let mut instances = self.instance_definitions.lock().unwrap(); - instances.insert(name, def); - } - - pub fn add_instance_uses(&self, new_defs: &mut Vec) { - let mut defs = self.instances.lock().unwrap(); - defs.append(new_defs); - } - - pub fn create_materials( - &self, - textures: &NamedTextures, - arena: &mut Arena, - ) -> (HashMap, Vec) { - let mut state = self.material_state.lock().unwrap(); - log::info!( - "Starting to consume {} normal map futures", - state.normal_map_jobs.len() - ); - - let jobs: Vec<_> = state.normal_maps_jobs.drain().collect(); - for (filename, job) in jobs { - if state.normal_map_jobs.contains_key(&filename) { - panic!("Trying to redefine '{}' twice in jobs and maps", filename); - } - - let image = job.wait(); - state.normal_map_jobs.insert(filename, image); - } - - let mut named_materials_out: HashMap = HashMap::new(); - - for (name, entity) in &state.named_materials { - if named_materials_out.contains_key(name) { - log::error!( - "{}: trying to redefine named material '{}'.", - entity.loc, - name - ); - continue; - } - - let mat_type = entity.parameters.get_one_string("type", ""); - if mat_type.is_empty() { - log::error!( - "{}: \"string type\" not provided in named material's parameters.", - entity.loc - ); - continue; - } - - let nm_filename = resolve_filename(&entity.parameters.get_one_string("normalmap", "")); - let mut normal_map_img = None; - - if !nm_filename.is_empty() { - if let Some(img) = state.normal_maps.get(&nm_filename) { - normal_map_img = Some(img.clone()); - } else { - panic!( - "Normal map '{}' was expected but not found in map.", - nm_filename - ); - } - } - - let tex_dict = TextureParameterDictionary::new(&entity.parameters, textures); - - let mat = Material::create( - name, - &tex_dict, - normal_map_img, - &named_materials_out, - &entity.loc, - arena, - ); - - named_materials_out.insert(name.clone(), mat); - } - - let mut materials_out: Vec = Vec::with_capacity(state.materials.len()); - for entity in &state.materials { - let nm_filename = resolve_filename(&entity.parameters.get_one_string("normalmap", "")); - let mut normal_map_img = None; - - if !nm_filename.is_empty() { - if let Some(img) = state.normal_maps.get(&nm_filename) { - normal_map_img = Some(img.clone()); - } else { - panic!( - "Normal map '{}' was expected but not found in map.", - nm_filename - ); - } - } - - let tex_dict = TextureParameterDictionary::new(&entity.parameters, textures); - - let mat = Material::create( - entity.materials.name, - &tex_dict, - normal_map_img, - &named_materials_out, - &entity.loc, - arena, - ); - } - (named_materials_out, materials_out) - } - - pub fn create_aggregate( - &self, - arena: &mut Arena, - textures: &NamedTextures, - shape_lights: &HashMap>, - ) -> Arc { - let shapes_guard = self.shapes.lock().unwrap(); - let animated_shapes_guard = self.animated_shapes.lock().unwrap(); - let media_guard = self.media_state.lock().unwrap(); - let mat_guard = self.material_state.lock().unwrap(); - - let lookup = SceneLookup { - textures: &textures.float_textures, - media: &media_guard.map, - named_materials: &mat_guard.named_materials.iter().cloned().collect(), - materials: &mat_guard.materials, - shape_lights, - }; - - let mut all_primitives = Vec::new(); - - // Parallel CPU Load - let loaded_static = self.load_shapes_parallel(&shapes_guard, &lookup); - // Serial Arena Upload - let static_prims = self.upload_shapes(arena, &shapes_guard, loaded_static, &lookup); - all_primitives.extend(static_prims); - - // Parallel CPU Load - let loaded_animated = self.load_animated_shapes_parallel(&animated_shapes_guard, &lookup); - // Serial Arena Upload - let anim_prims = - self.upload_animated_shapes(arena, &animated_shapes_guard, loaded_animated, &lookup); - all_primitives.extend(anim_prims); - - // (Similar pattern: Lock definitions, load parallel, upload serial) - // Call a helper `create_instance_primitives` here. - - if !all_primitives.is_empty() { - todo!("Build BVH or KD-Tree") - } else { - todo!("Return empty") - } - } - - fn load_shapes_parallel( - &self, - entities: &[ShapeSceneEntity], - lookup: &SceneLookup, - ) -> Vec> { - entities - .par_iter() - .map(|sh| { - Shape::create( - &sh.base.name, - sh.render_from_object.as_ref(), - sh.object_from_render.as_ref(), - sh.reverse_orientation, - &sh.base.parameters, - lookup.float_textures, - &sh.base.loc, - ) - }) - .collect() - } - - fn load_animated_shapes_parallel( - &self, - entities: &[AnimatedShapeSceneEntity], - lookup: &SceneLookup, - ) -> Vec> { - entities - .par_iter() - .map(|sh| { - Shape::create( - &sh.transformed_base.base.name, - &sh.identity, - &sh.identity, - sh.reverse_orientation, - &sh.transformed_base.base.parameters, - lookup.float_textures, - &sh.transformed_base.base.loc, - ) - }) - .collect() - } - - fn upload_shapes( - &self, - arena: &mut Arena, - entities: &[ShapeSceneEntity], - loaded_shapes: Vec>, - lookup: &SceneLookup, - ) -> Vec { - let mut primitives = Vec::new(); - - for (i, (entity, shapes)) in entities.iter().zip(loaded_shapes).enumerate() { - if shapes.is_empty() { - continue; - } - - let alpha_tex = self.get_alpha_texture( - &entity.base.parameters, - &entity.base.loc, - arena, - lookup.float_textures, - ); - - let mtl = lookup.resolve_material(&entity.material, &entity.base.loc); - let mi = MediumInterface::new( - lookup.find_medium(&entity.inside_medium, &entity.base.loc), - lookup.find_medium(&entity.outside_medium, &entity.base.loc), - ); - - let shape_lights_opt = lookup.shape_lights.get(&i); - - for (j, shape_host) in shapes.into_iter().enumerate() { - let mut area_light = None; - if entity.light_index.is_some() { - if let Some(lights) = shape_lights_opt { - if j < lights.len() { - area_light = Some(lights[j]); - } - } - } - - let shape_ptr = shape_host.upload(arena); - - let prim = if area_light.is_none() - && !mi.is_medium_transition() - && alpha_tex.is_none() - { - let p = SimplePrimitive::new(shape_ptr, mtl.unwrap()); - Primitive::Simple(arena.alloc(p)) - } else { - let p = - GeometricPrimitive::new(shape_ptr, mtl.unwrap(), area_light, mi, alpha_tex); - Primitive::Geometric(arena.alloc(p)) - }; - - primitives.push(prim); - } - } - primitives - } - - fn upload_animated_shapes( - &self, - arena: &mut Arena, - entities: &[AnimatedShapeSceneEntity], - loaded_shapes: Vec>, - lookup: &SceneLookup, - ) -> Vec { - // Logic mirrors upload_shapes, but constructs AnimatedPrimitive or BVH - // ... - // Note: For AnimatedPrimitives, you wrap the result in `arena.alloc(AnimatedPrimitive::new(...))` - Vec::new() - } - - fn get_alpha_texture( - &self, - params: &ParameterDictionary, - loc: &FileLoc, - arena: &mut Arena, - textures: &HashMap, - ) -> Option { - let alpha_name = params.get_texture("alpha"); - - if let Some(name) = alpha_name { - match textures.get(&name) { - Some(tex) => Some(*tex), - None => panic!("{:?}: Alpha texture '{}' not found", loc, name), - } - } else { - let alpha_val = params.get_one_float("alpha", 1.0); - if alpha_val < 1.0 { - let tex = FloatConstantTexture::new(alpha_val); - let ptr = arena.alloc(tex); - Some(FloatTexture::Constant(ptr)) - } else { - None - } - } - } - - pub fn get_camera(&self) -> Arc { - self.get_singleton(&self.camera_state, "Camera") - } - - pub fn get_sampler(&self) -> Arc { - self.get_singleton(&self.sampler_state, "Sampler") - } - - pub fn get_film(&self) -> Arc { - self.get_singleton(&self.film_state, "Film") - } - - pub fn done(&self) { - let shapes = self.shapes.lock().unwrap(); - let animated_shapes = self.animated_shapes.lock().unwrap(); - let instances = self.instances.lock().unwrap(); - let instance_defs = self.instance_definitions.lock().unwrap(); - - let tex_state = self.texture_state.lock().unwrap(); - let mat_state = self.material_state.lock().unwrap(); - let med_state = self.media_state.lock().unwrap(); - let light_state = self.light_state.lock().unwrap(); - - log::info!( - "Scene stats: {} shapes, {} animated shapes, {} instance definitions, \ - {} instance uses, {} float textures, {} spectrum textures, \ - {} named materials, {} materials", - shapes.len(), - animated_shapes.len(), - instance_defs.len(), - instances.len(), - // Count serial + async jobs for total textures - tex_state.serial_float_textures.len() + tex_state.float_texture_jobs.len(), - tex_state.serial_spectrum_textures.len() - + tex_state.async_spectrum_textures.len() - + tex_state.spectrum_texture_jobs.len(), - mat_state.named_materials.len(), - mat_state.materials.len() - ); - - { - let mut unused_float_tex: HashSet<&str> = HashSet::new(); - let mut unused_spec_tex: HashSet<&str> = HashSet::new(); - - for (name, _) in &tex_state.serial_float_textures { - unused_float_tex.insert(name); - } - for name in tex_state.float_texture_jobs.keys() { - unused_float_tex.insert(name); - } - - for (name, _) in &tex_state.serial_spectrum_textures { - unused_spec_tex.insert(name); - } - for (name, _) in &tex_state.async_spectrum_textures { - unused_spec_tex.insert(name); - } - for name in tex_state.spectrum_texture_jobs.keys() { - unused_spec_tex.insert(name); - } - - // Define the Checker Closure - let mut check_params = |params: &ParameterDictionary| { - for param in ¶ms.params { - if param.type_name == "texture" { - // Textures are usually stored in `strings` - for tex_name in ¶m.strings { - if unused_float_tex.contains(tex_name.as_str()) { - unused_float_tex.remove(tex_name.as_str()); - } else if unused_spec_tex.contains(tex_name.as_str()) { - unused_spec_tex.remove(tex_name.as_str()); - } - } - } - } - }; - - // Materials - for (_, mat) in &mat_state.named_materials { - check_params(&mat.parameters); - } - for mat in &mat_state.materials { - check_params(&mat.parameters); - } - - for (_, tex) in &tex_state.serial_float_textures { - check_params(&tex.parameters); - } - for (_, tex) in &tex_state.serial_spectrum_textures { - check_params(&tex.parameters); - } - - // Shapes - for shape in shapes.iter() { - check_params(&shape.parameters); - } - for anim_shape in animated_shapes.iter() { - check_params(&anim_shape.parameters); - } - - // Instance Definitions - for def in instance_defs.values() { - for shape in &def.shapes { - check_params(&shape.parameters); - } - for anim_shape in &def.animated_shapes { - check_params(&anim_shape.parameters); - } - } - - for name in unused_float_tex { - log::warn!("{}: float texture unused in scene", name); - } - for name in unused_spec_tex { - log::warn!("{}: spectrum texture unused in scene", name); - } - } - } - - // PRIVATE METHODS - fn get_singleton( - &self, - mutex: &Mutex>, - name: &str, - ) -> Arc { - let mut state = mutex.lock().unwrap(); - - if let Some(ref res) = state.result { - return res.clone(); - } - - if let Some(job) = state.job.take() { - let res = Arc::new(job.wait()); - state.result = Some(res.clone()); - log::debug!("Retrieved {} from future", name); - return res; - } - - panic!("{} requested but not initialized!", name); - } - - fn start_loading_normal_maps( - &self, - state: &mut MaterialState, - parameters: &ParameterDictionary, - ) { - let filename = resolve_filename(parameters.get_one_string("filename", "")); - if filename.is_empty() { - log::error!("Could not load normal maps"); - return; - } - - if state.normal_map_jobs.contains_key(&filename) - || state.normal_maps.contains_key(&filename) - { - return; - } - - let filename_clone = filename.clone(); - - let job = run_async(move || { - let path = std::path::Path::new(&filename_clone); - - let immeta = Image::read(path, Some(ColorEncoding::Linear)) - .unwrap_or_else(|e| panic!("{}: unable to read normal map: {}", filename_clone, e)); - - let image = &immeta.image; - - let rgb_desc = image.get_channel_desc(&["R", "G", "B"]).unwrap_or_else(|| { - panic!( - "{}: normal map must contain R, G, and B channels", - filename_clone - ) - }); - - let normal_map = image.select_channels(&rgb_desc); - - Arc::new(normal_map) - }); - - state.normal_map_jobs.insert(filename, job); - } - - fn get_medium(&self, name: &str, loc: &FileLoc) -> Option> { - if name.is_empty() { - return None; - } - - let mut state = self.media_state.lock().unwrap(); - - if let Some(medium) = state.map.get(name) { - return Some(medium.clone()); - } - - if let Some(job) = state.jobs.remove(name) { - let medium = Arc::new(job.wait()); - state.map.insert(name.to_string(), medium.clone()); - - return Some(medium); - } - - log::error!("{}: Medium \"{}\" is not defined.", loc, name); - - None - } -} - -const MAX_TRANSFORMS: usize = 2; - -#[derive(Debug, Default, Clone, Copy)] -struct TransformSet { - t: [Transform; MAX_TRANSFORMS], -} - -impl TransformSet { - pub fn is_animated(&self) -> bool { - self.t[0] != self.t[1] - } - - pub fn inverse(&self) -> Self { - Self { - t: [self.t[0].inverse(), self.t[1].inverse()], - } - } - - pub fn map(&mut self, bits: u32, f: F) - where - F: Fn(&Transform) -> Transform, - { - if (bits & 1) != 0 { - self.t[0] = f(&self.t[0]); - } - if (bits & 2) != 0 { - self.t[1] = f(&self.t[1]); - } - } -} - -impl IndexTrait for TransformSet { - type Output = Transform; - - fn index(&self, index: usize) -> &Self::Output { - &self.t[index] - } -} - -impl IndexMutTrait for TransformSet { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - &mut self.t[index] - } -} - -#[derive(Default, Debug, Clone)] -struct GraphicsState { - pub current_inside_medium: String, - pub current_outside_medium: String, - pub current_material_name: String, - pub current_material_index: Option, - pub area_light_name: String, - pub area_light_params: ParsedParameterVector, - pub area_light_loc: FileLoc, - pub shape_attributes: ParsedParameterVector, - pub light_attributes: ParsedParameterVector, - pub material_attributes: ParsedParameterVector, - pub medium_attributes: ParsedParameterVector, - pub texture_attributes: ParsedParameterVector, - - pub reverse_orientation: bool, - pub color_space: Option>, - - pub ctm: TransformSet, - // 1=Start, 2=End, 3=All - pub active_transform_bits: u32, - pub transform_start_time: Float, - pub transform_end_time: Float, -} - -#[derive(PartialEq, Eq)] -enum BlockState { - OptionsBlock, - WorldBlock, -} - -pub struct BasicSceneBuilder { - scene: Arc, - current_block: BlockState, - graphics_state: GraphicsState, - pushed_graphics_states: Vec, - push_stack: Vec<(char, FileLoc)>, - render_from_world: Transform, - named_coordinate_systems: HashMap, - active_instance_definition: Option, - - shapes: Mutex>, - animated_shapes: Mutex>, - - named_material_names: HashSet, - medium_names: HashSet, - current_camera: Option, - current_film: Option, - current_integrator: Option, - current_sampler: Option, - current_filter: Option, - current_accelerator: Option, -} - -impl BasicSceneBuilder { - pub const START_TRANSFORM_BITS: u32 = 1 << 0; - pub const END_TRANSFORM_BITS: u32 = 1 << 1; - pub const ALL_TRANSFORM_BITS: u32 = (1 << MAX_TRANSFORMS) - 1; - - pub fn new(scene: Arc) -> Self { - Self { - scene, - current_block: BlockState::OptionsBlock, - graphics_state: GraphicsState::default(), - pushed_graphics_states: Vec::new(), - push_stack: Vec::new(), - render_from_world: Transform::identity(), - named_coordinate_systems: HashMap::new(), - active_instance_definition: None, - shapes: Vec::new(), - animated_shapes: Vec::new(), - named_material_names: HashSet::new(), - medium_names: HashSet::new(), - - current_camera: Some(CameraSceneEntity { - base: SceneEntity { - name: "perspective".into(), - ..Default::default() - }, - camera_transform: CameraTransform::from_world( - AnimatedTransform::default(), - RenderingCoordinateSystem::World, - ), - medium: String::new(), - }), - current_sampler: Some(SceneEntity { - name: "zsobol".into(), - ..Default::default() - }), - current_filter: Some(SceneEntity { - name: "gaussian".into(), - ..Default::default() - }), - current_integrator: Some(SceneEntity { - name: "volpath".into(), - ..Default::default() - }), - current_accelerator: Some(SceneEntity { - name: "bvh".into(), - ..Default::default() - }), - current_film: Some(SceneEntity { - name: "rgb".into(), - ..Default::default() - }), - } - } - - fn for_active_transforms(&mut self, f: F) - where - F: Fn(&Transform) -> Transform, - { - let bits = self.graphics_state.active_transform_bits; - - if (bits & 1) != 0 { - self.graphics_state.ctm.t[0] = f(&self.graphics_state.ctm.t[0]); - } - if (bits & 2) != 0 { - self.graphics_state.ctm.t[1] = f(&self.graphics_state.ctm.t[1]); - } - } - - fn verify_world(&self, name: &str, loc: &FileLoc) { - if self.current_block != BlockState::WorldBlock { - eprintln!("Error: {} not allowed outside WorldBlock at {}", name, loc); - } - } - - fn verify_options(&self, name: &str, loc: &FileLoc) { - if self.current_block != BlockState::OptionsBlock { - eprintln!("Error: {} not allowed inside WorldBlock at {}", name, loc); - } - } -} - -impl ParserTarget for BasicSceneBuilder { - fn reverse_orientation(&mut self, loc: FileLoc) { - self.verify_world("ReverseOrientation", &loc); - self.graphics_state.reverse_orientation = !self.graphics_state.reverse_orientation; - } - - fn color_space(&mut self, name: &str, loc: FileLoc) { - let _ = match RGBColorSpace::get_named(name) { - Ok(cs) => { - self.graphics_state.color_space = Some(cs); - } - Err(_) => { - eprintln!("Error: Color space '{}' unknown at {}", name, loc); - } - }; - } - - fn identity(&mut self, _loc: FileLoc) { - self.for_active_transforms(|_| Transform::identity()); - } - - fn translate(&mut self, dx: Float, dy: Float, dz: Float, _loc: FileLoc) { - let t = Transform::translate(Vector3f::new(dx, dy, dz)); - self.for_active_transforms(|cur| cur * &t); - } - - fn rotate(&mut self, angle: Float, ax: Float, ay: Float, az: Float, _loc: FileLoc) { - let t = Transform::rotate_around_axis(angle, Vector3f::new(ax, ay, az)); - self.for_active_transforms(|cur| cur * &t); - } - - fn scale(&mut self, sx: Float, sy: Float, sz: Float, _loc: FileLoc) { - let t = Transform::scale(sx, sy, sz); - self.for_active_transforms(|cur| cur * &t); - } - - fn look_at( - &mut self, - ex: Float, - ey: Float, - ez: Float, - lx: Float, - ly: Float, - lz: Float, - ux: Float, - uy: Float, - uz: Float, - loc: FileLoc, - ) { - let result = look_at((ex, ey, ez), (lx, ly, lz), (ux, uy, uz)); - match result { - Ok(t) => { - self.for_active_transforms(|cur| cur * &t); - } - Err(e) => { - eprintln!("Error: {} at {}", e, loc); - } - } - } - - fn concat_transform(&mut self, m: &[Float; 16], loc: FileLoc) { - let result = Transform::from_flat(m); - match result { - Ok(t) => { - self.for_active_transforms(|cur| cur * &t); - } - Err(e) => { - eprintln!("Error: {} at {}", e, loc); - } - } - } - - fn transform(&mut self, m: &[Float; 16], loc: FileLoc) { - let result = Transform::from_flat(m); - match result { - Ok(t) => { - self.for_active_transforms(|_| t); - } - Err(e) => { - eprintln!("Error: {} at {}", e, loc); - } - } - } - - fn coordinate_system(&mut self, name: &str, _loc: FileLoc) { - self.named_coordinate_systems - .insert(name.to_string(), self.graphics_state.ctm); - } - - fn coord_sys_transform(&mut self, name: &str, loc: FileLoc) { - if let Some(saved_ctm) = self.named_coordinate_systems.get(name) { - self.graphics_state.ctm = *saved_ctm; - } else { - eprintln!( - "Warning: Couldn't find named coordinate system \"{}\" at {}", - name, loc - ); - } - } - - fn camera(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { - self.verify_options("Camera", &loc); - - let camera_from_world = self.graphics_state.ctm; - - let world_from_camera = camera_from_world.inverse(); - - self.named_coordinate_systems - .insert("camera".to_string(), world_from_camera); - - let animated_world_from_cam = AnimatedTransform::new( - &world_from_camera.t[0], - self.graphics_state.transform_start_time, - &world_from_camera.t[1], - self.graphics_state.transform_end_time, - ); - - let rendering_space = RenderingCoordinateSystem::CameraWorld; - let camera_transform = - CameraTransform::from_world(animated_world_from_cam, rendering_space); - self.render_from_world = camera_from_world.t[0]; - - let parameters = - ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); - - self.current_camera = Some(CameraSceneEntity { - base: SceneEntity { - name: name.to_string(), - loc, - parameters, - }, - camera_transform, - - medium: self.graphics_state.current_outside_medium.clone(), - }); - } - - fn active_transform_all(&mut self, _loc: FileLoc) { - self.graphics_state.active_transform_bits = Self::ALL_TRANSFORM_BITS; - } - - fn active_transform_end_time(&mut self, _loc: FileLoc) { - self.graphics_state.active_transform_bits = Self::END_TRANSFORM_BITS; - } - fn active_transform_start_time(&mut self, _loc: FileLoc) { - self.graphics_state.active_transform_bits = Self::START_TRANSFORM_BITS; - } - - fn transform_times(&mut self, start: Float, end: Float, loc: FileLoc) { - self.verify_options("TransformTimes", &loc); - self.graphics_state.transform_start_time = start; - self.graphics_state.transform_end_time = end; - } - - fn option(&mut self, _name: &str, _value: &str, _loc: FileLoc) { - todo!() - } - - fn pixel_filter(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { - let parameters = - ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); - self.verify_options("PixelFilter", &loc); - self.current_filter = Some(SceneEntity { - name: name.to_string(), - loc, - parameters, - }); - } - - fn film(&mut self, type_name: &str, params: &ParsedParameterVector, loc: FileLoc) { - let parameters = - ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); - self.verify_options("Film", &loc); - self.current_filter = Some(SceneEntity { - name: type_name.to_string(), - loc, - parameters, - }); - } - - fn accelerator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { - let parameters = - ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); - self.verify_options("PixelFilter", &loc); - self.current_filter = Some(SceneEntity { - name: name.to_string(), - loc, - parameters, - }); - } - - fn integrator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { - let parameters = - ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); - self.verify_options("PixelFilter", &loc); - self.current_filter = Some(SceneEntity { - name: name.to_string(), - loc, - parameters, - }); - } - - fn make_named_medium(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { - let curr_name = normalize_utf8(name); - self.verify_world("MakeNamedMaterial", &loc); - - if !self.named_material_names.insert(curr_name.to_string()) { - eprintln!("Error: {}: named material '{}' redefined.", loc, name); - return; - } - - let parameters = - ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); - - let entity = SceneEntity { - name: name.to_string(), - loc, - parameters, - }; - - self.scene - .named_materials - .lock() - .push((curr_name.to_string(), entity)); - } - - fn medium_interface(&mut self, inside_name: &str, outside_name: &str, _loc: FileLoc) { - let inside = normalize_utf8(inside_name); - let outside = normalize_utf8(outside_name); - - self.graphics_state.current_inside_medium = inside; - self.graphics_state.current_outside_medium = outside; - } - - fn sampler(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { - let parameters = - ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); - self.verify_options("Sampler", &loc); - self.current_sampler = Some(SceneEntity { - name: name.to_string(), - loc, - parameters, - }) - } - - fn world_begin(&mut self, loc: FileLoc) { - self.verify_options("WorldBegin", &loc); - self.current_block = BlockState::WorldBlock; - for i in 0..MAX_TRANSFORMS { - self.graphics_state.ctm[i] = Transform::default(); - } - self.graphics_state.active_transform_bits = Self::ALL_TRANSFORM_BITS; - self.named_coordinate_systems - .insert("world".to_string(), self.graphics_state.ctm); - let scene = Arc::clone(&self.scene); - scene.set_options( - self.current_filter - .take() - .expect("Filter not set before WorldBegin"), - self.current_film - .take() - .expect("Film not set before WorldBegin"), - self.current_camera - .take() - .expect("Camera not set before WorldBegin"), - self.current_sampler - .take() - .expect("Sampler not set before WorldBegin"), - self.current_integrator - .take() - .expect("Integrator not set before WorldBegin"), - self.current_accelerator - .take() - .expect("Accelerator not set before WorldBegin"), - ); - } - - fn attribute_begin(&mut self, loc: FileLoc) { - self.verify_world("AttributeBegin", &loc); - self.pushed_graphics_states - .push(self.graphics_state.clone()); - self.push_stack.push(('a', loc)); - } - - fn attribute_end(&mut self, loc: FileLoc) { - self.verify_world("AttributeEnd", &loc); - if self.pushed_graphics_states.is_empty() { - log::error!( - "[{:?}] Unmatched AttributeEnd encountered. Ignoring it.", - loc - ); - return; - } - - if let Some(state) = self.pushed_graphics_states.pop() { - self.graphics_state = state; - } - - if let Some((kind, start_loc)) = self.push_stack.pop() { - if kind == 'o' { - log::error!( - "[{:?}] Mismatched nesting: open ObjectBegin from {} at AttributeEnd", - loc, - start_loc - ); - std::process::exit(1); - } else { - debug_assert_eq!(kind, 'a', "Expected AttributeBegin on the stack"); - } - } - } - - fn attribute(&mut self, target: &str, params: ParsedParameterVector, loc: FileLoc) { - let current_attributes = match target { - "shape" => &mut self.graphics_state.shape_attributes, - "light" => &mut self.graphics_state.light_attributes, - "material" => &mut self.graphics_state.material_attributes, - "medium" => &mut self.graphics_state.medium_attributes, - "texture" => &mut self.graphics_state.texture_attributes, - _ => { - log::error!( - "[{:?}] Unknown attribute target \"{}\". Must be \"shape\", \"light\", \ - \"material\", \"medium\", or \"texture\".", - loc, - target - ); - return; - } - }; - - let active_color_space = self.graphics_state.color_space.clone(); - - for mut p in params { - p.may_be_unused = true; - p.color_space = active_color_space.clone(); - - current_attributes.push(p.clone()); - } - } - - fn texture( - &mut self, - orig_name: &str, - type_name: &str, - tex_name: &str, - params: &ParsedParameterVector, - loc: FileLoc, - ) { - let name = normalize_utf8(orig_name); - self.verify_world("Texture", &loc); - let dict = ParameterDictionary::from_array( - params.clone(), - &self.graphics_state.texture_attributes, - self.graphics_state.color_space.clone(), - ); - - if type_name != "float" && type_name != "spectrum" { - self.error_exit_deferred( - &loc, - &format!( - "{}: texture type unknown. Must be \"float\" or \"spectrum\".", - tex_name - ), - ); - return; - } - - { - let names = if type_name == "float" { - &mut self.float_texture_names - } else { - &mut self.spectrum_texture_names - }; - - if names.contains(name) { - self.error_exit_deferred(&loc, &format!("Redefining texture \"{}\".", name)); - return; - } - names.insert(name.to_string()); - } - - let base = SceneEntity { - name: tex_name, - parameters: dict, - loc, - }; - let entity = TextureSceneEntity { - base, - render_from_object: self.graphics_state.render_from_object.clone(), - }; - - if type_name == "float" { - self.scene.add_float_texture(name.to_string(), entity); - } else { - self.scene.add_spectrum_texture(name.to_string(), entity); - } - } - - fn material(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { - self.verify_world("material", loc); - let entity = SceneEntity { - name, - loc, - parameters: params, - }; - self.graphics_state.current_material_name = self.scene.add_material(name, entity); - } - fn make_named_material(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) { - todo!() - } - fn named_material(&mut self, _name: &str, _loc: FileLoc) { - todo!() - } - fn light_source(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) { - todo!() - } - fn area_light_source(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) { - todo!() - } - fn shape(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) { - todo!() - } - fn object_begin(&mut self, _name: &str, _loc: FileLoc) { - todo!() - } - fn object_end(&mut self, _loc: FileLoc) { - todo!() - } - fn object_instance(&mut self, _name: &str, _loc: FileLoc) { - todo!() - } - fn end_of_files(&mut self) { - todo!() - } -} diff --git a/src/core/scene/builder.rs b/src/core/scene/builder.rs new file mode 100644 index 0000000..470960e --- /dev/null +++ b/src/core/scene/builder.rs @@ -0,0 +1,615 @@ +use super::BasicScene; +use super::entities::*; +use crate::utils::error::FileLoc; +use crate::utils::normalize_utf8; +use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector}; +use crate::utils::parser::ParserTarget; +use shared::Float; +use shared::core::camera::CameraTransform; +use shared::core::options::RenderingCoordinateSystem; +use shared::spectra::RGBColorSpace; +use shared::utils::transform::{AnimatedTransform, Transform, look_at}; +use std::collections::{HashMap, HashSet}; +use std::ops::{Index, IndexMut}; +use std::sync::Arc; + +const MAX_TRANSFORMS: usize = 2; + +#[derive(Debug, Default, Clone, Copy)] +struct TransformSet { + t: [Transform; MAX_TRANSFORMS], +} + +impl TransformSet { + fn is_animated(&self) -> bool { + self.t[0] != self.t[1] + } + + fn inverse(&self) -> Self { + Self { + t: [self.t[0].inverse(), self.t[1].inverse()], + } + } +} + +impl Index for TransformSet { + type Output = Transform; + fn index(&self, i: usize) -> &Self::Output { + &self.t[i] + } +} + +impl IndexMut for TransformSet { + fn index_mut(&mut self, i: usize) -> &mut Self::Output { + &mut self.t[i] + } +} + +#[derive(Default, Debug, Clone)] +struct GraphicsState { + pub current_inside_medium: String, + pub current_outside_medium: String, + pub current_material_name: String, + pub current_material_index: Option, + pub area_light_name: String, + pub area_light_params: ParsedParameterVector, + pub area_light_loc: FileLoc, + pub shape_attributes: ParsedParameterVector, + pub light_attributes: ParsedParameterVector, + pub material_attributes: ParsedParameterVector, + pub medium_attributes: ParsedParameterVector, + pub texture_attributes: ParsedParameterVector, + pub reverse_orientation: bool, + pub color_space: Option>, + pub ctm: TransformSet, + pub active_transform_bits: u32, + pub transform_start_time: Float, + pub transform_end_time: Float, +} + +#[derive(PartialEq, Eq)] +enum BlockState { + OptionsBlock, + WorldBlock, +} + +pub struct BasicSceneBuilder { + scene: Arc, + current_block: BlockState, + graphics_state: GraphicsState, + pushed_graphics_states: Vec, + push_stack: Vec<(char, FileLoc)>, + render_from_world: Transform, + named_coordinate_systems: HashMap, + active_instance_definition: Option, + + float_texture_names: HashSet, + spectrum_texture_names: HashSet, + named_material_names: HashSet, + medium_names: HashSet, + + current_camera: Option, + current_film: Option, + current_integrator: Option, + current_sampler: Option, + current_filter: Option, + current_accelerator: Option, +} + +impl BasicSceneBuilder { + pub const START_TRANSFORM_BITS: u32 = 1 << 0; + pub const END_TRANSFORM_BITS: u32 = 1 << 1; + pub const ALL_TRANSFORM_BITS: u32 = (1 << MAX_TRANSFORMS) - 1; + + pub fn new(scene: Arc) -> Self { + Self { + scene, + current_block: BlockState::OptionsBlock, + graphics_state: GraphicsState { + active_transform_bits: Self::ALL_TRANSFORM_BITS, + ..Default::default() + }, + pushed_graphics_states: Vec::new(), + push_stack: Vec::new(), + render_from_world: Transform::identity(), + named_coordinate_systems: HashMap::new(), + active_instance_definition: None, + float_texture_names: HashSet::new(), + spectrum_texture_names: HashSet::new(), + named_material_names: HashSet::new(), + medium_names: HashSet::new(), + current_camera: Some(CameraSceneEntity { + base: SceneEntity { + name: "perspective".into(), + ..Default::default() + }, + camera_transform: CameraTransform::from_world( + AnimatedTransform::default(), + RenderingCoordinateSystem::World, + ), + medium: String::new(), + }), + current_sampler: Some(SceneEntity { + name: "zsobol".into(), + ..Default::default() + }), + current_filter: Some(SceneEntity { + name: "gaussian".into(), + ..Default::default() + }), + current_integrator: Some(SceneEntity { + name: "volpath".into(), + ..Default::default() + }), + current_accelerator: Some(SceneEntity { + name: "bvh".into(), + ..Default::default() + }), + current_film: Some(SceneEntity { + name: "rgb".into(), + ..Default::default() + }), + } + } + + fn for_active_transforms(&mut self, f: F) + where + F: Fn(&Transform) -> Transform, + { + let bits = self.graphics_state.active_transform_bits; + if (bits & 1) != 0 { + self.graphics_state.ctm.t[0] = f(&self.graphics_state.ctm.t[0]); + } + if (bits & 2) != 0 { + self.graphics_state.ctm.t[1] = f(&self.graphics_state.ctm.t[1]); + } + } + + fn verify_world(&self, name: &str, loc: &FileLoc) { + if self.current_block != BlockState::WorldBlock { + log::error!("{}: {} not allowed outside WorldBlock", loc, name); + } + } + + fn verify_options(&self, name: &str, loc: &FileLoc) { + if self.current_block != BlockState::OptionsBlock { + log::error!("{}: {} not allowed inside WorldBlock", loc, name); + } + } +} + +impl ParserTarget for BasicSceneBuilder { + fn reverse_orientation(&mut self, loc: FileLoc) { + self.verify_world("ReverseOrientation", &loc); + self.graphics_state.reverse_orientation = !self.graphics_state.reverse_orientation; + } + + fn color_space(&mut self, name: &str, loc: FileLoc) { + let _ = match RGBColorSpace::get_named(name) { + Ok(cs) => { + self.graphics_state.color_space = Some(cs); + } + Err(_) => { + eprintln!("Error: Color space '{}' unknown at {}", name, loc); + } + }; + } + + fn identity(&mut self, _loc: FileLoc) { + self.for_active_transforms(|_| Transform::identity()); + } + + fn translate(&mut self, dx: Float, dy: Float, dz: Float, _loc: FileLoc) { + let t = Transform::translate(Vector3f::new(dx, dy, dz)); + self.for_active_transforms(|cur| cur * &t); + } + + fn rotate(&mut self, angle: Float, ax: Float, ay: Float, az: Float, _loc: FileLoc) { + let t = Transform::rotate_around_axis(angle, Vector3f::new(ax, ay, az)); + self.for_active_transforms(|cur| cur * &t); + } + + fn scale(&mut self, sx: Float, sy: Float, sz: Float, _loc: FileLoc) { + let t = Transform::scale(sx, sy, sz); + self.for_active_transforms(|cur| cur * &t); + } + + fn look_at( + &mut self, + ex: Float, + ey: Float, + ez: Float, + lx: Float, + ly: Float, + lz: Float, + ux: Float, + uy: Float, + uz: Float, + loc: FileLoc, + ) { + let result = look_at((ex, ey, ez), (lx, ly, lz), (ux, uy, uz)); + match result { + Ok(t) => { + self.for_active_transforms(|cur| cur * &t); + } + Err(e) => { + eprintln!("Error: {} at {}", e, loc); + } + } + } + + fn concat_transform(&mut self, m: &[Float; 16], loc: FileLoc) { + let result = Transform::from_flat(m); + match result { + Ok(t) => { + self.for_active_transforms(|cur| cur * &t); + } + Err(e) => { + eprintln!("Error: {} at {}", e, loc); + } + } + } + + fn transform(&mut self, m: &[Float; 16], loc: FileLoc) { + let result = Transform::from_flat(m); + match result { + Ok(t) => { + self.for_active_transforms(|_| t); + } + Err(e) => { + eprintln!("Error: {} at {}", e, loc); + } + } + } + + fn coordinate_system(&mut self, name: &str, _loc: FileLoc) { + self.named_coordinate_systems + .insert(name.to_string(), self.graphics_state.ctm); + } + + fn coord_sys_transform(&mut self, name: &str, loc: FileLoc) { + if let Some(saved_ctm) = self.named_coordinate_systems.get(name) { + self.graphics_state.ctm = *saved_ctm; + } else { + eprintln!( + "Warning: Couldn't find named coordinate system \"{}\" at {}", + name, loc + ); + } + } + + fn camera(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { + self.verify_options("Camera", &loc); + + let camera_from_world = self.graphics_state.ctm; + + let world_from_camera = camera_from_world.inverse(); + + self.named_coordinate_systems + .insert("camera".to_string(), world_from_camera); + + let animated_world_from_cam = AnimatedTransform::new( + &world_from_camera.t[0], + self.graphics_state.transform_start_time, + &world_from_camera.t[1], + self.graphics_state.transform_end_time, + ); + + let rendering_space = RenderingCoordinateSystem::CameraWorld; + let camera_transform = + CameraTransform::from_world(animated_world_from_cam, rendering_space); + self.render_from_world = camera_from_world.t[0]; + + let parameters = + ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); + + self.current_camera = Some(CameraSceneEntity { + base: SceneEntity { + name: name.to_string(), + loc, + parameters, + }, + camera_transform, + + medium: self.graphics_state.current_outside_medium.clone(), + }); + } + + fn active_transform_all(&mut self, _loc: FileLoc) { + self.graphics_state.active_transform_bits = Self::ALL_TRANSFORM_BITS; + } + + fn active_transform_end_time(&mut self, _loc: FileLoc) { + self.graphics_state.active_transform_bits = Self::END_TRANSFORM_BITS; + } + fn active_transform_start_time(&mut self, _loc: FileLoc) { + self.graphics_state.active_transform_bits = Self::START_TRANSFORM_BITS; + } + + fn transform_times(&mut self, start: Float, end: Float, loc: FileLoc) { + self.verify_options("TransformTimes", &loc); + self.graphics_state.transform_start_time = start; + self.graphics_state.transform_end_time = end; + } + + fn option(&mut self, _name: &str, _value: &str, _loc: FileLoc) { + todo!() + } + + fn pixel_filter(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { + let parameters = + ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); + self.verify_options("PixelFilter", &loc); + self.current_filter = Some(SceneEntity { + name: name.to_string(), + loc, + parameters, + }); + } + + fn film(&mut self, type_name: &str, params: &ParsedParameterVector, loc: FileLoc) { + let parameters = + ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); + self.verify_options("Film", &loc); + self.current_filter = Some(SceneEntity { + name: type_name.to_string(), + loc, + parameters, + }); + } + + fn accelerator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { + let parameters = + ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); + self.verify_options("PixelFilter", &loc); + self.current_filter = Some(SceneEntity { + name: name.to_string(), + loc, + parameters, + }); + } + + fn integrator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { + let parameters = + ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); + self.verify_options("PixelFilter", &loc); + self.current_filter = Some(SceneEntity { + name: name.to_string(), + loc, + parameters, + }); + } + + fn make_named_medium(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { + let curr_name = normalize_utf8(name); + self.verify_world("MakeNamedMaterial", &loc); + + if !self.named_material_names.insert(curr_name.to_string()) { + eprintln!("Error: {}: named material '{}' redefined.", loc, name); + return; + } + + let parameters = + ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); + + let entity = SceneEntity { + name: name.to_string(), + loc, + parameters, + }; + + self.scene + .named_materials + .lock() + .push((curr_name.to_string(), entity)); + } + + fn medium_interface(&mut self, inside_name: &str, outside_name: &str, _loc: FileLoc) { + let inside = normalize_utf8(inside_name); + let outside = normalize_utf8(outside_name); + + self.graphics_state.current_inside_medium = inside; + self.graphics_state.current_outside_medium = outside; + } + + fn sampler(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { + let parameters = + ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); + self.verify_options("Sampler", &loc); + self.current_sampler = Some(SceneEntity { + name: name.to_string(), + loc, + parameters, + }) + } + + fn world_begin(&mut self, loc: FileLoc) { + self.verify_options("WorldBegin", &loc); + self.current_block = BlockState::WorldBlock; + for i in 0..MAX_TRANSFORMS { + self.graphics_state.ctm[i] = Transform::default(); + } + self.graphics_state.active_transform_bits = Self::ALL_TRANSFORM_BITS; + self.named_coordinate_systems + .insert("world".to_string(), self.graphics_state.ctm); + let scene = Arc::clone(&self.scene); + scene.set_options( + self.current_filter + .take() + .expect("Filter not set before WorldBegin"), + self.current_film + .take() + .expect("Film not set before WorldBegin"), + self.current_camera + .take() + .expect("Camera not set before WorldBegin"), + self.current_sampler + .take() + .expect("Sampler not set before WorldBegin"), + self.current_integrator + .take() + .expect("Integrator not set before WorldBegin"), + self.current_accelerator + .take() + .expect("Accelerator not set before WorldBegin"), + ); + } + + fn attribute_begin(&mut self, loc: FileLoc) { + self.verify_world("AttributeBegin", &loc); + self.pushed_graphics_states + .push(self.graphics_state.clone()); + self.push_stack.push(('a', loc)); + } + + fn attribute_end(&mut self, loc: FileLoc) { + self.verify_world("AttributeEnd", &loc); + if self.pushed_graphics_states.is_empty() { + log::error!( + "[{:?}] Unmatched AttributeEnd encountered. Ignoring it.", + loc + ); + return; + } + + if let Some(state) = self.pushed_graphics_states.pop() { + self.graphics_state = state; + } + + if let Some((kind, start_loc)) = self.push_stack.pop() { + if kind == 'o' { + log::error!( + "[{:?}] Mismatched nesting: open ObjectBegin from {} at AttributeEnd", + loc, + start_loc + ); + std::process::exit(1); + } else { + debug_assert_eq!(kind, 'a', "Expected AttributeBegin on the stack"); + } + } + } + + fn attribute(&mut self, target: &str, params: ParsedParameterVector, loc: FileLoc) { + let current_attributes = match target { + "shape" => &mut self.graphics_state.shape_attributes, + "light" => &mut self.graphics_state.light_attributes, + "material" => &mut self.graphics_state.material_attributes, + "medium" => &mut self.graphics_state.medium_attributes, + "texture" => &mut self.graphics_state.texture_attributes, + _ => { + log::error!( + "[{:?}] Unknown attribute target \"{}\". Must be \"shape\", \"light\", \ + \"material\", \"medium\", or \"texture\".", + loc, + target + ); + return; + } + }; + + let active_color_space = self.graphics_state.color_space.clone(); + + for mut p in params { + p.may_be_unused = true; + p.color_space = active_color_space.clone(); + + current_attributes.push(p.clone()); + } + } + + fn texture( + &mut self, + orig_name: &str, + type_name: &str, + tex_name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) { + let name = normalize_utf8(orig_name); + self.verify_world("Texture", &loc); + let dict = ParameterDictionary::from_array( + params.clone(), + &self.graphics_state.texture_attributes, + self.graphics_state.color_space.clone(), + ); + + if type_name != "float" && type_name != "spectrum" { + self.error_exit_deferred( + &loc, + &format!( + "{}: texture type unknown. Must be \"float\" or \"spectrum\".", + tex_name + ), + ); + return; + } + + { + let names = if type_name == "float" { + &mut self.float_texture_names + } else { + &mut self.spectrum_texture_names + }; + + if names.contains(name) { + self.error_exit_deferred(&loc, &format!("Redefining texture \"{}\".", name)); + return; + } + names.insert(name.to_string()); + } + + let base = SceneEntity { + name: tex_name, + parameters: dict, + loc, + }; + let entity = TextureSceneEntity { + base, + render_from_object: self.graphics_state.render_from_object.clone(), + }; + + if type_name == "float" { + self.scene.add_float_texture(name.to_string(), entity); + } else { + self.scene.add_spectrum_texture(name.to_string(), entity); + } + } + + fn material(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { + self.verify_world("material", loc); + let entity = SceneEntity { + name, + loc, + parameters: params, + }; + self.graphics_state.current_material_name = self.scene.add_material(name); + } + fn make_named_material(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) { + todo!() + } + fn named_material(&mut self, _name: &str, _loc: FileLoc) { + todo!() + } + fn light_source(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) { + todo!() + } + fn area_light_source(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) { + todo!() + } + fn shape(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) { + todo!() + } + fn object_begin(&mut self, _name: &str, _loc: FileLoc) { + todo!() + } + fn object_end(&mut self, _loc: FileLoc) { + todo!() + } + fn object_instance(&mut self, _name: &str, _loc: FileLoc) { + todo!() + } + fn end_of_files(&mut self) { + todo!() + } +} diff --git a/src/core/scene/entities.rs b/src/core/scene/entities.rs new file mode 100644 index 0000000..2f7abed --- /dev/null +++ b/src/core/scene/entities.rs @@ -0,0 +1,81 @@ +#[derive(Clone, Debug)] +pub enum MaterialRef { + Index(usize), + Name(String), + None, +} + +#[derive(Clone, Default, Debug)] +pub struct SceneEntity { + pub name: String, + pub loc: FileLoc, + pub parameters: ParameterDictionary, +} + +#[derive(Clone, Debug)] +pub struct TransformedSceneEntity { + pub base: SceneEntity, + pub render_from_object: AnimatedTransform, +} + +pub type MediumSceneEntity = TransformedSceneEntity; +pub type TextureSceneEntity = TransformedSceneEntity; + +#[derive(Clone, Debug)] +pub struct CameraSceneEntity { + pub base: SceneEntity, + pub camera_transform: CameraTransform, + pub medium: String, +} + +#[derive(Clone, Debug)] +pub struct ShapeSceneEntity { + pub base: SceneEntity, + pub render_from_object: Arc, + pub object_from_render: Arc, + pub reverse_orientation: bool, + pub material: MaterialRef, + pub light_index: Option, + + pub inside_medium: String, + pub outside_medium: String, +} + +#[derive(Clone, Debug)] +pub struct AnimatedShapeSceneEntity { + pub transformed_base: TransformedSceneEntity, + + pub identity: Arc, + pub reverse_orientation: bool, + pub material: MaterialRef, + pub light_index: Option, + pub inside_medium: String, + pub outside_medium: String, +} + +#[derive(Clone, Debug)] +pub struct InstanceDefinitionSceneEntity { + pub name: String, + pub loc: FileLoc, + pub shapes: Vec, + pub animated_shapes: Vec, +} + +#[derive(Clone, Debug)] +pub struct LightSceneEntity { + pub transformed_base: TransformedSceneEntity, + pub medium: String, +} + +#[derive(Clone, Debug)] +pub enum InstanceTransform { + Animated(AnimatedTransform), + Static(Arc), +} + +#[derive(Clone, Debug)] +pub struct InstanceSceneEntity { + pub name: String, + pub loc: FileLoc, + pub transform: InstanceTransform, +} diff --git a/src/core/scene/mod.rs b/src/core/scene/mod.rs new file mode 100644 index 0000000..9b00488 --- /dev/null +++ b/src/core/scene/mod.rs @@ -0,0 +1,9 @@ +mod builder; +mod entities; +mod scene; +mod state; + +pub use builder::BasicSceneBuilder; +pub use entities::*; +pub use scene::{BasicScene, SceneLookup}; +pub use state::*; diff --git a/src/core/scene/scene.rs b/src/core/scene/scene.rs new file mode 100644 index 0000000..19c5d19 --- /dev/null +++ b/src/core/scene/scene.rs @@ -0,0 +1,732 @@ +use super::entities::*; +use super::state::*; +use crate::core::filter::FilterFactory; +use crate::core::image::{Image, io::ImageIO}; +use crate::core::material::MaterialFactory; +use crate::core::texture::{FloatTexture, SpectrumTexture}; +use crate::utils::arena::Arena; +use crate::utils::error::FileLoc; +use crate::utils::parallel::{AsyncJob, run_async}; +use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary}; +use crate::utils::{Upload, resolve_filename}; +use parking_lot::Mutex; +use rayon::prelude::*; +use shared::core::camera::Camera; +use shared::core::color::ColorEncoding; +use shared::core::film::Film; +use shared::core::filter::Filter; +use shared::core::light::Light; +use shared::core::material::Material; +use shared::core::medium::{Medium, MediumInterface}; +use shared::core::primitive::{GeometricPrimitive, Primitive, PrimitiveTrait, SimplePrimitive}; +use shared::core::sampler::Sampler; +use shared::core::shape::Shape; +use shared::core::texture::SpectrumType; +use shared::spectra::RGBColorSpace; +use shared::textures::FloatConstantTexture; +use std::collections::HashMap; +use std::sync::Arc; + +pub struct SceneLookup<'a> { + pub textures: &'a NamedTextures, + pub media: &'a HashMap>, + pub named_materials: &'a HashMap, + pub materials: &'a Vec, + pub shape_lights: &'a HashMap>, +} + +impl<'a> SceneLookup<'a> { + pub fn find_medium(&self, name: &str, loc: &FileLoc) -> Option> { + if name.is_empty() { + return None; + } + self.media.get(name).cloned().or_else(|| { + panic!("{}: medium '{}' not defined", loc, name); + }) + } + + pub fn resolve_material(&self, mat_ref: &MaterialRef, _loc: &FileLoc) -> Option { + match mat_ref { + MaterialRef::Name(name) => { + Some(*self.named_materials.get(name).expect("Material not found")) + } + MaterialRef::Index(idx) => Some(self.materials[*idx]), + MaterialRef::None => None, + } + } +} + +pub struct BasicScene { + pub integrator: Mutex>, + pub accelerator: Mutex>, + pub film_colorspace: Mutex>>, + + pub shapes: Mutex>, + pub animated_shapes: Mutex>, + + pub instances: Mutex>, + pub instance_definitions: Mutex>>, + + pub media_state: Mutex, + pub material_state: Mutex, + pub light_state: Mutex, + pub texture_state: Mutex, + + pub camera_state: Mutex>, + pub sampler_state: Mutex>, + pub film_state: Mutex>, +} + +impl BasicScene { + pub fn new() -> Self { + Self { + integrator: Mutex::new(None), + accelerator: Mutex::new(None), + film_colorspace: Mutex::new(None), + shapes: Mutex::new(Vec::new()), + animated_shapes: Mutex::new(Vec::new()), + instances: Mutex::new(Vec::new()), + instance_definitions: Mutex::new(HashMap::new()), + media_state: Mutex::new(MediaState::default()), + material_state: Mutex::new(MaterialState::default()), + light_state: Mutex::new(LightState::default()), + texture_state: Mutex::new(TextureState::default()), + camera_state: Mutex::new(SingletonState::default()), + sampler_state: Mutex::new(SingletonState::default()), + film_state: Mutex::new(SingletonState::default()), + } + } + + // ======================================================================== + // Options + // ======================================================================== + + pub fn set_options( + self: &Arc, + filter: SceneEntity, + film: SceneEntity, + camera: CameraSceneEntity, + sampler: SceneEntity, + integ: SceneEntity, + accel: SceneEntity, + ) { + *self.integrator.lock() = Some(integ); + *self.accelerator.lock() = Some(accel); + + if let Some(cs) = film.parameters.color_space.as_ref() { + *self.film_colorspace.lock() = Some(Arc::clone(cs)); + } + + let filt = Filter::create(&filter.name, &filter.parameters, &filter.loc); + let shutter_close = camera.base.parameters.get_one_float("shutterclose", 1.); + let shutter_open = camera.base.parameters.get_one_float("shutteropen", 0.); + let exposure_time = shutter_close - shutter_open; + + let film_instance = Arc::new( + Film::create( + &film.name, + &film.parameters, + exposure_time, + filt.expect("Must have a filter"), + Some(camera.camera_transform.clone()), + &film.loc, + ) + .expect("Must have a film"), + ); + + *self.film_state.lock() = SingletonState { + result: Some(Arc::clone(&film_instance)), + job: None, + }; + + let sampler_film = Arc::clone(&film_instance); + let sampler_job = run_async(move || { + let res = sampler_film.as_ref().base().full_resolution; + Sampler::create(&sampler.name, &sampler.parameters, res, &sampler.loc) + .expect("Sampler was not correctly created") + }); + self.sampler_state.lock().job = Some(sampler_job); + + let camera_film = Arc::clone(&film_instance); + let scene_ptr = Arc::clone(self); + let camera_job = run_async(move || { + let medium = scene_ptr.get_medium(&camera.medium, &camera.base.loc); + Camera::create( + &camera.base.name, + &camera.base.parameters, + medium, + &camera.camera_transform, + camera_film, + &camera.base.loc, + ) + .expect("Failed to create camera") + }); + self.camera_state.lock().job = Some(camera_job); + } + + // ======================================================================== + // Add methods + // ======================================================================== + + pub fn add_named_material(&self, name: &str, material: SceneEntity) { + let mut state = self.material_state.lock(); + self.start_loading_normal_maps(&mut state, &material.parameters); + state.named_materials.push((name.to_string(), material)); + } + + pub fn add_material(&self, material: SceneEntity) -> usize { + let mut state = self.material_state.lock(); + self.start_loading_normal_maps(&mut state, &material.parameters); + state.materials.push(material); + state.materials.len() - 1 + } + + pub fn add_float_texture(&self, name: String, texture: TextureSceneEntity) { + if texture.render_from_object.is_animated() { + log::info!( + "{}: Animated world to texture not supported, using start", + texture.base.loc + ); + } + + let mut state = self.texture_state.lock(); + + if texture.base.name != "imagemap" && texture.base.name != "ptex" { + state.serial_float_textures.push((name, texture)); + return; + } + + let filename = resolve_filename(&texture.base.parameters.get_one_string("filename", "")); + if filename.is_empty() { + log::error!( + "[{:?}] \"string filename\" not provided for image texture.", + texture.base.loc + ); + state.n_missing_textures += 1; + return; + } + + if !std::path::Path::new(&filename).exists() { + log::error!("[{:?}] {}: file not found.", texture.base.loc, filename); + state.n_missing_textures += 1; + return; + } + + if state.loading_texture_filenames.contains(&filename) { + state.serial_float_textures.push((name, texture)); + return; + } + + state.loading_texture_filenames.insert(filename.clone()); + + let texture_clone = texture.clone(); + let job = run_async(move || { + let render_from_texture = texture_clone.render_from_object.start_transform(); + let tex_dict = TextureParameterDictionary::new(&texture_clone.base.parameters, None); + + Arc::new(FloatTexture::create( + &texture_clone.base.name, + &render_from_texture, + &tex_dict, + &texture_clone.base.loc, + )) + }); + + state.float_texture_jobs.insert(name, job); + } + + pub fn add_spectrum_texture(&self, name: String, texture: TextureSceneEntity) { + if texture.render_from_object.is_animated() { + log::info!( + "{}: Animated world to texture not supported, using start", + texture.base.loc + ); + } + + let mut state = self.texture_state.lock(); + + if texture.base.name != "imagemap" && texture.base.name != "ptex" { + state.serial_spectrum_textures.push((name, texture)); + return; + } + + let filename = resolve_filename(&texture.base.parameters.get_one_string("filename", "")); + if filename.is_empty() { + log::error!( + "[{:?}] \"string filename\" not provided for image texture.", + texture.base.loc + ); + state.n_missing_textures += 1; + return; + } + + if !std::path::Path::new(&filename).exists() { + log::error!("[{:?}] {}: file not found.", texture.base.loc, filename); + state.n_missing_textures += 1; + return; + } + + if state.loading_texture_filenames.contains(&filename) { + state.serial_spectrum_textures.push((name, texture)); + return; + } + + state.loading_texture_filenames.insert(filename.clone()); + + let texture_clone = texture.clone(); + let job = run_async(move || { + let render_from_texture = texture_clone.render_from_object.start_transform(); + let tex_dict = TextureParameterDictionary::new(&texture_clone.base.parameters, None); + + Arc::new(SpectrumTexture::create( + &texture_clone.base.name, + &render_from_texture, + &tex_dict, + SpectrumType::Albedo, + &texture_clone.base.loc, + )) + }); + + state.spectrum_texture_jobs.insert(name, job); + } + + pub fn add_area_light(&self, light: SceneEntity) -> usize { + let mut state = self.light_state.lock(); + state.area_lights.push(light); + state.area_lights.len() - 1 + } + + pub fn add_shapes(&self, new_shapes: Vec) { + self.shapes.lock().extend(new_shapes); + } + + pub fn add_animated_shapes(&self, new_shapes: Vec) { + self.animated_shapes.lock().extend(new_shapes); + } + + pub fn add_instance_definition(&self, instance: InstanceDefinitionSceneEntity) { + let name = instance.name.clone(); + self.instance_definitions + .lock() + .insert(name, Arc::new(instance)); + } + + pub fn add_instance_uses(&self, uses: Vec) { + self.instances.lock().extend(uses); + } + + // ======================================================================== + // Finalization: Textures + // ======================================================================== + + pub fn create_textures(&self) -> NamedTextures { + let mut state = self.texture_state.lock(); + + let mut float_textures: HashMap> = HashMap::new(); + let mut spectrum_textures: HashMap> = HashMap::new(); + + // Collect async jobs + for (name, job) in state.float_texture_jobs.drain() { + float_textures.insert(name, job.wait()); + } + + for (name, job) in state.spectrum_texture_jobs.drain() { + spectrum_textures.insert(name, job.wait()); + } + + // Create serial textures (need access to already-loaded textures) + let named = NamedTextures { + float_textures: float_textures.clone(), + spectrum_textures: spectrum_textures.clone(), + }; + + for (name, entity) in state.serial_float_textures.drain(..) { + let render_from_texture = entity.render_from_object.start_transform(); + let tex_dict = TextureParameterDictionary::new(&entity.base.parameters, Some(&named)); + let tex = FloatTexture::create( + &entity.base.name, + &render_from_texture, + &tex_dict, + &entity.base.loc, + ); + float_textures.insert(name, Arc::new(tex)); + } + + for (name, entity) in state.serial_spectrum_textures.drain(..) { + let render_from_texture = entity.render_from_object.start_transform(); + let tex_dict = TextureParameterDictionary::new(&entity.base.parameters, Some(&named)); + let tex = SpectrumTexture::create( + &entity.base.name, + &render_from_texture, + &tex_dict, + SpectrumType::Albedo, + &entity.base.loc, + ); + spectrum_textures.insert(name, Arc::new(tex)); + } + + NamedTextures { + float_textures, + spectrum_textures, + } + } + + // ======================================================================== + // Finalization: Materials + // ======================================================================== + + pub fn create_materials( + &self, + textures: &NamedTextures, + ) -> (HashMap, Vec) { + let mut state = self.material_state.lock(); + + // Resolve normal map jobs + let jobs: Vec<_> = state.normal_map_jobs.drain().collect(); + for (filename, job) in jobs { + let image = job.wait(); + state.normal_maps.insert(filename, image); + } + + let mut named_materials: HashMap = HashMap::new(); + + for (name, entity) in &state.named_materials { + if named_materials.contains_key(name) { + log::error!( + "{}: trying to redefine named material '{}'.", + entity.loc, + name + ); + continue; + } + + let mat_type = entity.parameters.get_one_string("type", ""); + if mat_type.is_empty() { + log::error!( + "{}: \"string type\" not provided in named material's parameters.", + entity.loc + ); + continue; + } + + let normal_map = self.get_normal_map(&state, &entity.parameters); + let tex_dict = TextureParameterDictionary::new(&entity.parameters, Some(textures)); + + let mat = Material::create( + &mat_type, + &tex_dict, + normal_map, + &named_materials, + &entity.loc, + ); + + named_materials.insert(name.clone(), mat); + } + + let mut materials: Vec = Vec::with_capacity(state.materials.len()); + + for entity in &state.materials { + let normal_map = self.get_normal_map(&state, &entity.parameters); + let tex_dict = TextureParameterDictionary::new(&entity.parameters, Some(textures)); + + let mat = Material::create( + &entity.name, + &tex_dict, + normal_map, + &named_materials, + &entity.loc, + ); + materials.push(mat); + } + + (named_materials, materials) + } + + // ======================================================================== + // Finalization: Aggregate + // ======================================================================== + + pub fn create_aggregate( + &self, + arena: &mut Arena, + textures: &NamedTextures, + named_materials: &HashMap, + materials: &Vec, + shape_lights: &HashMap>, + ) -> Vec { + let shapes = self.shapes.lock(); + let animated_shapes = self.animated_shapes.lock(); + let media = self.media_state.lock(); + + let lookup = SceneLookup { + textures, + media: &media.map, + named_materials, + materials, + shape_lights, + }; + + let mut primitives = Vec::new(); + + // Static shapes: parallel load, serial upload + let loaded = self.load_shapes_parallel(&shapes, &lookup); + primitives.extend(self.upload_shapes(arena, &shapes, loaded, &lookup)); + + // Animated shapes + let loaded_anim = self.load_animated_shapes_parallel(&animated_shapes, &lookup); + primitives.extend(self.upload_animated_shapes( + arena, + &animated_shapes, + loaded_anim, + &lookup, + )); + + primitives + } + + fn load_shapes_parallel( + &self, + entities: &[ShapeSceneEntity], + lookup: &SceneLookup, + ) -> Vec> { + entities + .par_iter() + .map(|sh| { + Shape::create( + &sh.base.name, + sh.render_from_object.as_ref(), + sh.object_from_render.as_ref(), + sh.reverse_orientation, + &sh.base.parameters, + &lookup.textures.float_textures, + &sh.base.loc, + ) + }) + .collect() + } + + fn load_animated_shapes_parallel( + &self, + entities: &[AnimatedShapeSceneEntity], + lookup: &SceneLookup, + ) -> Vec> { + entities + .par_iter() + .map(|sh| { + Shape::create( + &sh.transformed_base.base.name, + sh.identity.as_ref(), + sh.identity.as_ref(), + sh.reverse_orientation, + &sh.transformed_base.base.parameters, + &lookup.textures.float_textures, + &sh.transformed_base.base.loc, + ) + }) + .collect() + } + + fn upload_shapes( + &self, + arena: &mut Arena, + entities: &[ShapeSceneEntity], + loaded: Vec>, + lookup: &SceneLookup, + ) -> Vec { + let mut primitives = Vec::new(); + + for (i, (entity, shapes)) in entities.iter().zip(loaded).enumerate() { + if shapes.is_empty() { + continue; + } + + let alpha_tex = self.get_alpha_texture( + &entity.base.parameters, + &entity.base.loc, + &lookup.textures.float_textures, + ); + + let mtl = lookup.resolve_material(&entity.material, &entity.base.loc); + + let mi = MediumInterface::new( + lookup.find_medium(&entity.inside_medium, &entity.base.loc), + lookup.find_medium(&entity.outside_medium, &entity.base.loc), + ); + + let shape_lights_opt = lookup.shape_lights.get(&i); + + for (j, shape) in shapes.into_iter().enumerate() { + let mut area_light = None; + if entity.light_index.is_some() { + if let Some(lights) = shape_lights_opt { + if j < lights.len() { + area_light = Some(lights[j].clone()); + } + } + } + + let shape_ptr = shape.upload(arena); + + let prim = + if area_light.is_none() && !mi.is_medium_transition() && alpha_tex.is_none() { + let p = SimplePrimitive::new(shape_ptr, mtl); + Primitive::Simple(arena.alloc(p)) + } else { + let p = GeometricPrimitive::new( + shape_ptr, + mtl, + area_light, + mi.clone(), + alpha_tex.clone(), + ); + Primitive::Geometric(arena.alloc(p)) + }; + + primitives.push(prim); + } + } + + primitives + } + + fn upload_animated_shapes( + &self, + _arena: &mut Arena, + _entities: &[AnimatedShapeSceneEntity], + _loaded: Vec>, + _lookup: &SceneLookup, + ) -> Vec { + // TODO: implement animated shape upload + Vec::new() + } + + // ======================================================================== + // Getters + // ======================================================================== + + pub fn get_camera(&self) -> Arc { + self.get_singleton(&self.camera_state, "Camera") + } + + pub fn get_sampler(&self) -> Arc { + self.get_singleton(&self.sampler_state, "Sampler") + } + + pub fn get_film(&self) -> Arc { + self.get_singleton(&self.film_state, "Film") + } + + // ======================================================================== + // Private helpers + // ======================================================================== + + fn get_singleton( + &self, + state: &Mutex>, + name: &str, + ) -> Arc { + let mut guard = state.lock(); + + if let Some(ref res) = guard.result { + return res.clone(); + } + + if let Some(job) = guard.job.take() { + let res = Arc::new(job.wait()); + guard.result = Some(res.clone()); + return res; + } + + panic!("{} requested but not initialized!", name); + } + + fn start_loading_normal_maps(&self, state: &mut MaterialState, params: &ParameterDictionary) { + let filename = resolve_filename(¶ms.get_one_string("normalmap", "")); + if filename.is_empty() { + return; + } + + if state.normal_map_jobs.contains_key(&filename) + || state.normal_maps.contains_key(&filename) + { + return; + } + + let filename_clone = filename.clone(); + let job = run_async(move || { + let path = std::path::Path::new(&filename_clone); + let immeta = Image::read(path, Some(ColorEncoding::Linear)) + .unwrap_or_else(|e| panic!("{}: unable to read normal map: {}", filename_clone, e)); + + let rgb_desc = immeta + .image + .get_channel_desc(&["R", "G", "B"]) + .unwrap_or_else(|| { + panic!( + "{}: normal map must contain R, G, B channels", + filename_clone + ) + }); + + Arc::new(immeta.image.select_channels(&rgb_desc)) + }); + + state.normal_map_jobs.insert(filename, job); + } + + fn get_normal_map( + &self, + state: &MaterialState, + params: &ParameterDictionary, + ) -> Option> { + let filename = resolve_filename(¶ms.get_one_string("normalmap", "")); + if filename.is_empty() { + return None; + } + state.normal_maps.get(&filename).cloned() + } + + fn get_alpha_texture( + &self, + params: &ParameterDictionary, + loc: &FileLoc, + textures: &HashMap>, + ) -> Option> { + if let Some(name) = params.get_texture("alpha") { + match textures.get(&name) { + Some(tex) => Some(tex.clone()), + None => panic!("{:?}: Alpha texture '{}' not found", loc, name), + } + } else { + let alpha_val = params.get_one_float("alpha", 1.0); + if alpha_val < 1.0 { + Some(Arc::new(FloatTexture::Constant(FloatConstantTexture::new( + alpha_val, + )))) + } else { + None + } + } + } + + pub fn get_medium(&self, name: &str, loc: &FileLoc) -> Option> { + if name.is_empty() { + return None; + } + + let mut state = self.media_state.lock(); + + if let Some(medium) = state.map.get(name) { + return Some(medium.clone()); + } + + if let Some(job) = state.jobs.remove(name) { + let medium = Arc::new(job.wait()); + state.map.insert(name.to_string(), medium.clone()); + return Some(medium); + } + + log::error!("{}: Medium \"{}\" is not defined.", loc, name); + None + } +} diff --git a/src/core/scene/state.rs b/src/core/scene/state.rs new file mode 100644 index 0000000..da140fd --- /dev/null +++ b/src/core/scene/state.rs @@ -0,0 +1,36 @@ +#[derive(Default)] +pub struct TextureState { + pub serial_float_textures: Vec<(String, TextureSceneEntity)>, + pub serial_spectrum_textures: Vec<(String, TextureSceneEntity)>, + pub async_spectrum_textures: Vec<(String, TextureSceneEntity)>, + pub loading_texture_filenames: HashSet, + pub float_texture_jobs: HashMap>>, + pub spectrum_texture_jobs: HashMap>>, + pub n_missing_textures: i32, +} + +#[derive(Default)] +pub struct MaterialState { + pub named_materials: Vec<(String, SceneEntity)>, + pub materials: Vec, + pub normal_map_jobs: HashMap>>, + pub normal_maps: HashMap>, +} + +#[derive(Default)] +pub struct LightState { + pub light_jobs: Vec>, + pub area_lights: Vec, +} + +#[derive(Default)] +pub struct MediaState { + pub jobs: HashMap>, + pub map: HashMap>, +} + +#[derive(Default)] +pub struct SingletonState { + pub result: Option>, + pub job: Option>, +} diff --git a/src/core/texture.rs b/src/core/texture.rs index cf70f9e..a1316a9 100644 --- a/src/core/texture.rs +++ b/src/core/texture.rs @@ -42,6 +42,12 @@ pub enum FloatTexture { Wrinkled(WrinkledTexture), } +impl FloatTextureTrait for Arc { + fn evaluate(&self, ctx: &TextureEvalContext) -> Float { + self.as_ref().evaluate(ctx) + } +} + impl FloatTexture { pub fn create( name: &str, @@ -119,10 +125,16 @@ pub enum SpectrumTexture { Mix(SpectrumMixTexture), DirectionMix(SpectrumDirectionMixTexture), Dots(SpectrumDotsTexture), - Ptex(SpectrumPtexTexture), + // Ptex(SpectrumPtexTexture), Scaled(SpectrumScaledTexture), } +impl SpectrumTextureTrait for Arc { + fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum { + self.as_ref().evaluate(ctx, lambda) + } +} + pub trait CreateTextureMapping { fn create( params: &TextureParameterDictionary, diff --git a/src/gpu/driver.rs b/src/gpu/driver.rs deleted file mode 100644 index db0c5bd..0000000 --- a/src/gpu/driver.rs +++ /dev/null @@ -1,320 +0,0 @@ -use bytemuck::{Pod, Zeroable}; -use cust::context::{CacheConfig, CurrentContext, ResourceLimit}; -use cust::device::DeviceAttribute; -use cust::memory::DeviceCopy; -use cust::prelude::*; -use lazy_static::lazy_static; -use parking_lot::Mutex; -use std::error::Error; -use std::sync::Arc; - -use shared::Float; -use shared::core::geometry::{Normal, Point, Vector}; -use shared::core::options::get_options; -use shared::spectra::{SampledSpectrum, SampledWavelengths}; -use shared::utils::interval::Interval; - -#[macro_export] -macro_rules! impl_gpu_traits { - ($name:ty) => { - unsafe impl DeviceCopy for $name {} - unsafe impl Zeroable for $name {} - unsafe impl Pod for $name {} - }; -} - -#[macro_export] -macro_rules! impl_math_gpu_traits { - ($Struct:ident) => { - #[cfg(feature = "use_gpu")] - unsafe impl DeviceCopy for $Struct where T: DeviceCopy + Copy {} - - unsafe impl Zeroable for $Struct where T: Zeroable {} - - unsafe impl Pod for $Struct where T: Pod {} - }; -} - -impl_math_gpu_traits!(Vector); -impl_math_gpu_traits!(Normal); -impl_math_gpu_traits!(Point); -impl_gpu_traits!(Interval); -impl_gpu_traits!(Float4); -impl_gpu_traits!(SampledSpectrum); -impl_gpu_traits!(SampledWavelengths); - -#[repr(C, align(16))] -#[derive(Clone, Copy, Debug, Default, PartialEq)] -pub struct Float4 { - pub v: [f32; 4], -} -pub type Vec4 = Vector; - -impl From for Float4 { - #[inline] - fn from(vec: Vector) -> Self { - Self { v: vec.0 } - } -} - -impl From for Vec4 { - #[inline] - fn from(storage: Float4) -> Self { - Vector(storage.v) - } -} - -struct KernelStats { - description: String, - num_launches: usize, - sum_ms: f32, - min_ms: f32, - max_ms: f32, -} - -impl KernelStats { - fn new(description: &str) -> Self { - Self { - description: description.to_string(), - num_launches: 0, - sum_ms: 0.0, - min_ms: 0.0, - max_ms: 0.0, - } - } -} - -struct ProfilerEvent { - start: Event, - stop: Event, - active: bool, - stats: Option>>, -} - -impl ProfilerEvent { - fn new() -> Result { - let start = Event::new(EventFlags::DEFAULT)?; - let stop = Event::new(EventFlags::DEFAULT)?; - Ok(Self { - start, - stop, - active: false, - stats: None, - }) - } - - fn sync(&mut self) { - if !self.active { - return; - } - - if self.stop.synchronize().is_ok() { - match self.stop.elapsed_time_f32(&self.start) { - Ok(ms) => { - if let Some(stats_arc) = &self.stats { - let mut stats = stats_arc.lock(); - stats.num_launches += 1; - if stats.num_launches == 1 { - stats.sum_ms = ms; - stats.min_ms = ms; - stats.max_ms = ms; - } else { - stats.sum_ms += ms; - stats.min_ms = stats.min_ms.min(ms); - stats.max_ms = stats.max_ms.max(ms); - } - } - } - Err(e) => log::error!("Failed to get elapsed time: {:?}", e), - } - } - self.active = false; - } -} - -struct Profiler { - kernel_stats: Vec>>, - event_pool: Vec, - pool_offset: usize, -} - -impl Profiler { - fn new() -> Self { - Self { - kernel_stats: Vec::new(), - event_pool: Vec::new(), - pool_offset: 0, - } - } - - fn prepare<'a>(&'a mut self, description: &str) -> &'a mut ProfilerEvent { - if self.event_pool.is_empty() { - for _ in 0..128 { - if let Ok(e) = ProfilerEvent::new() { - self.event_pool.push(e); - } - } - } - - if self.pool_offset >= self.event_pool.len() { - self.pool_offset = 0; - } - - let idx = self.pool_offset; - self.pool_offset += 1; - - let pe = &mut self.event_pool[idx]; - - if pe.active { - pe.sync(); - } - - pe.active = true; - pe.stats = None; - - let mut found = None; - for s in &self.kernel_stats { - if s.lock().description == description { - found = Some(s.clone()); - break; - } - } - - if found.is_none() { - let new_stats = Arc::new(Mutex::new(KernelStats::new(description))); - self.kernel_stats.push(new_stats.clone()); - found = Some(new_stats); - } - - pe.stats = found; - pe - } -} - -pub struct GpuState { - pub context: Context, - pub stream: Stream, - profiler: Profiler, -} - -impl GpuState { - fn init(device_index: u32) -> Result> { - match cust::init(CudaFlags::empty()) { - Ok(_) => {} - Err(cust::error::CudaError::SharedObjectInitFailed) => {} - Err(e) => return Err(Box::new(e)), - } - - let device = Device::get_device(device_index)?; - let name = device.name().unwrap_or_else(|_| "Unknown".into()); - - log::info!("Selected GPU: {}", name); - - let has_unified = device - .get_attribute(DeviceAttribute::UnifiedAddressing) - .unwrap_or(0); - if has_unified == 0 { - panic!("Selected GPU does not support unified addressing."); - } - - let context = Context::new(device)?; - - CurrentContext::set_resource_limit(ResourceLimit::StackSize, 8192)?; - CurrentContext::set_resource_limit(ResourceLimit::PrintfFifoSize, 32 * 1024 * 1024)?; - CurrentContext::set_cache_config(CacheConfig::PreferL1)?; - - let stream = Stream::new(StreamFlags::DEFAULT, None)?; - - Ok(Self { - context, - stream, - profiler: Profiler::new(), - }) - } -} - -lazy_static! { - pub static ref GPU_STATE: Mutex> = Mutex::new(None); -} - -pub fn gpu_init() { - if !get_options().use_gpu { - return; - } - - let device_id = get_options().gpu_device.unwrap_or(0); - log::info!("Initializing GPU Device {}", device_id); - - match GpuState::init(device_id) { - Ok(state) => { - #[cfg(feature = "use_nvtx")] - nvtx::name_thread("MAIN_THREAD"); - *GPU_STATE.lock() = Some(state); - } - Err(e) => { - panic!("Failed to initialize GPU: {:?}", e); - } - } -} - -pub fn gpu_thread_init() { - if let Some(state) = GPU_STATE.lock().as_ref() { - if let Err(e) = CurrentContext::set_current(&state.context) { - log::error!("Failed to set CUDA context for thread: {:?}", e); - } - } -} - -const SANITY_PTX: &str = r#" -.version 6.5 -.target sm_30 -.address_size 64 -.visible .entry scale_array_kernel(.param .u64 ptr, .param .u32 n, .param .f32 scale) { - .reg .u64 %ptr; .reg .u32 %n; .reg .f32 %scale; - .reg .u32 %tid; .reg .u32 %ntid; .reg .u32 %ctid; .reg .u32 %idx; - .reg .pred %p; .reg .u64 %addr; .reg .f32 %val; - - ld.param.u64 %ptr, [ptr]; - ld.param.u32 %n, [n]; - ld.param.f32 %scale, [scale]; - - mov.u32 %tid, %tid.x; - mov.u32 %ntid, %ntid.x; - mov.u32 %ctid, %ctaid.x; - mad.lo.s32 %idx, %ctid, %ntid, %tid; - - setp.ge.u32 %p, %idx, %n; - @%p ret; - - mul.wide.u32 %addr, %idx, 4; - add.u64 %addr, %ptr, %addr; - ld.global.f32 %val, [%addr]; - mul.f32 %val, %val, %scale; - st.global.f32 [%addr], %val; -} -"#; - -pub fn launch_scale_kernel(ptr: *mut f32, len: usize, scale: f32) -> Result<(), Box> { - // Note: quick_init works best for isolated tests. - // If the main engine runs, this might conflict, but for the sanity test it is correct. - let _ctx = cust::quick_init()?; - - let module = Module::from_str(SANITY_PTX)?; - let stream = Stream::new(StreamFlags::NON_BLOCKING, None)?; - let mut kernel = module.get_function("scale_array_kernel")?; - - let block_size = 256; - let grid_size = (len as u32 + block_size - 1) / block_size; - - unsafe { - launch!( - kernel<<>>( - ptr as u64, - len as u32, - scale - ) - )?; - } - stream.synchronize()?; - Ok(()) -} diff --git a/src/gpu/memory.rs b/src/gpu/memory.rs deleted file mode 100644 index 392df55..0000000 --- a/src/gpu/memory.rs +++ /dev/null @@ -1,57 +0,0 @@ -use cudarc::driver::{CudaDevice, CudaSlice, DeviceSlice}; - -use super::{GpuError, gpu_unwrap}; - -/// Device-only buffer (faster, but not CPU-accessible) -pub struct DeviceBuffer { - inner: CudaSlice, -} - -impl DeviceBuffer { - /// Allocate uninitialized - pub fn new(len: usize) -> Result { - let ctx = gpu_unwrap(); - let inner = unsafe { ctx.device.alloc::(len)? }; - Ok(Self { inner }) - } - - /// Allocate and copy from host slice - pub fn from_slice(data: &[T]) -> Result { - let ctx = gpu_unwrap(); - let inner = ctx.device.htod_sync_copy(data)?; - Ok(Self { inner }) - } - - /// Copy back to host - pub fn to_vec(&self) -> Result, GpuError> { - let ctx = gpu_unwrap(); - Ok(ctx.device.dtoh_sync_copy(&self.inner)?) - } - - /// Raw device pointer (for kernel params) - pub fn as_ptr(&self) -> *mut T { - *self.inner.device_ptr() as *mut T - } - - pub fn len(&self) -> usize { - self.inner.len() - } - - pub fn is_empty(&self) -> bool { - self.inner.len() == 0 - } - - /// Get the underlying CudaSlice (for cudarc APIs) - pub fn as_cuda_slice(&self) -> &CudaSlice { - &self.inner - } - - pub fn as_cuda_slice_mut(&mut self) -> &mut CudaSlice { - &mut self.inner - } -} - -/// Unified memory buffer (CPU + GPU accessible) -/// Note: cudarc doesn't have built-in unified memory, -/// so we use raw CUDA calls or just use DeviceBuffer + explicit copies -pub type UnifiedBuffer = DeviceBuffer; diff --git a/src/gpu/mod.rs b/src/gpu/mod.rs index 55cd4b9..81d7230 100644 --- a/src/gpu/mod.rs +++ b/src/gpu/mod.rs @@ -1,11 +1,19 @@ -#[cfg(feature = "use_gpu")] mod context; -#[cfg(feature = "use_gpu")] -mod memory; -#[cfg(feature = "use_gpu")] + +pub use context::{GpuState, gpu_init, gpu_state, gpu_state_or_panic, gpu_thread_init}; + pub mod wavefront; -#[cfg(feature = "use_gpu")] -pub use context::{GpuContext, GpuError, gpu, gpu_init, gpu_unwrap}; -#[cfg(feature = "use_gpu")] -pub use memory::UnifiedBuffer; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum GpuError { + #[error("CUDA driver error: {0}")] + Driver(#[from] cudarc::driver::DriverError), + #[error("No GPU context initialized")] + NoContext, +} + +pub fn gpu_unwrap() -> &'static GpuContext { + context::GPU_CONTEXT.get().expect("GPU not initialized") +} diff --git a/src/lights/diffuse.rs b/src/lights/diffuse.rs index b368a29..21f403f 100644 --- a/src/lights/diffuse.rs +++ b/src/lights/diffuse.rs @@ -1,31 +1,32 @@ +use std::path::Path; + use crate::core::image::{Image, ImageIO}; use crate::core::light::{CreateLight, lookup_spectrum}; use crate::core::spectrum::spectrum_to_photometric; -use crate::core::texture::{FloatTexture, FloatTextureTrait}; +use crate::core::texture::FloatTexture; use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename}; -use log::error; +use anyhow::{Result, anyhow}; use shared::core::geometry::Point2i; use shared::core::light::{Light, LightBase, LightType}; use shared::core::medium::{Medium, MediumInterface}; -use shared::core::shape::Shape; -use shared::core::spectrum::{Spectrum, SpectrumTrait}; +use shared::core::shape::{Shape, ShapeTrait}; +use shared::core::spectrum::Spectrum; use shared::core::texture::{SpectrumType, TextureEvalContext}; use shared::lights::DiffuseAreaLight; use shared::spectra::RGBColorSpace; use shared::utils::{Ptr, Transform}; use shared::{Float, PI}; -use std::fmt::Error; pub trait CreateDiffuseLight { fn new( - render_from_light: shared::utils::Transform, + render_from_light: Transform, medium_interface: MediumInterface, le: Spectrum, scale: Float, shape: Ptr, - alpha: Ptr, - image: Ptr, - colorspace: Option, + alpha: Ptr, + image: Ptr, + colorspace: Ptr, two_sided: bool, fov: Float, ) -> Self; @@ -33,19 +34,19 @@ pub trait CreateDiffuseLight { impl CreateDiffuseLight for DiffuseAreaLight { fn new( - render_from_light: shared::utils::Transform, + render_from_light: Transform, medium_interface: MediumInterface, le: Spectrum, scale: Float, shape: Ptr, - alpha: Ptr, - image: Ptr, - colorspace: Option, + alpha: Ptr, + image: Ptr, + colorspace: Ptr, two_sided: bool, fov: Float, ) -> Self { let is_constant_zero = match &alpha { - FloatTexture::FloatConstant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0, + FloatTexture::Constant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0, _ => false, }; @@ -55,11 +56,12 @@ impl CreateDiffuseLight for DiffuseAreaLight { (LightType::Area, Some(alpha)) }; - let base = LightBase::new(light_type, &render_from_light, &medium_interface); + let base = LightBase::new(light_type, render_from_light, medium_interface); - let lemit = lookup_spectrum(&le); - if let Some(im) = &image { - let desc = im + let lemit = Ptr::from(&lookup_spectrum(&le)); + + if !image.is_null() { + let desc = image .get_channel_desc(&["R", "G", "B"]) .expect("Image used for DiffuseAreaLight doesn't have R, G, B channels"); @@ -70,11 +72,13 @@ impl CreateDiffuseLight for DiffuseAreaLight { ); assert!( - colorspace.is_some(), + !colorspace.is_null(), "Image provided but ColorSpace is missing" ); } - let is_triangle_or_bilinear = matches!(shape, Shape::Triangle(_) | Shape::BilinearPatch(_)); + + let is_triangle_or_bilinear = + matches!(*shape, Shape::Triangle(_) | Shape::BilinearPatch(_)); if render_from_light.has_scale(None) && !is_triangle_or_bilinear { println!( "Scaling detected in rendering to light space transformation! \ @@ -108,44 +112,49 @@ impl CreateLight for DiffuseAreaLight { shape: &Shape, alpha: &FloatTexture, colorspace: Option<&RGBColorSpace>, - ) -> Result { + ) -> Result { let mut l = params.get_one_spectrum("l", None, SpectrumType::Illuminant); + let illum_spec = Spectrum::Dense(colorspace.unwrap().illuminant); let mut scale = params.get_one_float("scale", 1.); let two_sided = params.get_one_bool("twosided", false); - let filename = resolve_filename(params.get_one_string("filename", "")); + let filename = resolve_filename(¶ms.get_one_string("filename", "")); let (image, image_color_space) = if !filename.is_empty() { if l.is_some() { - return Err(error!(loc, "both \"L\" and \"filename\" specified")); + return Err(anyhow!(loc, "both \"L\" and \"filename\" specified")); } - let im = Image::read(&filename, None)?; + let im = Image::read(Path::new(&filename), None)?; if im.image.has_any_infinite_pixels() { - return Err(error!(loc, "{}: image has infinite pixel values", filename)); + return Err(anyhow!( + loc, + "{}: image has infinite pixel values", + filename + )); } if im.image.has_any_nan_pixels() { - return Err(error!(loc, "{}: image has NaN pixel values", filename)); + return Err(anyhow!(loc, "{}: image has NaN pixel values", filename)); } let channel_desc = im .image .get_channel_desc(&["R", "G", "B"]) - .ok_or_else(|| error!(loc, "{}: image must have R, G, B channels", filename))?; + .map_err(|_| anyhow!(loc, "{}: image must have R, G, B channels", filename))?; let image = im.image.select_channels(&channel_desc); - let cs = im.metadata.get_color_space(); + let cs = im.metadata.get_colorspace(); - (Some(image), Some(cs)) + (Some(image), cs) } else { if l.is_none() { - l = Some(colorspace.illuminant.clone()); + l = Some(illum_spec); } (None, None) }; - let l_for_scale = l.as_ref().unwrap_or(&colorspace.illuminant); - scale /= spectrum_to_photometric(l_for_scale); + let l_for_scale = l.as_ref().unwrap_or(&illum_spec); + scale /= spectrum_to_photometric(*l_for_scale); let phi_v = params.get_one_float("power", -1.0); if phi_v > 0.0 { @@ -158,13 +167,13 @@ impl CreateLight for DiffuseAreaLight { if let Some(ref img) = image { // Get the appropriate luminance vector from the image colour space - let lum_vec = image_color_space.luminance_vector(); + let lum_vec = image_color_space.unwrap().luminance_vector(); let mut sum_k_e = 0.0; - let res = img.resolution; + let res = img.resolution(); - for y in 0..res.y { - for x in 0..res.x { + for y in 0..res.y() { + for x in 0..res.x() { let r = img.get_channel(Point2i::new(x, y), 0); let g = img.get_channel(Point2i::new(x, y), 1); let b = img.get_channel(Point2i::new(x, y), 2); @@ -172,7 +181,7 @@ impl CreateLight for DiffuseAreaLight { sum_k_e += r * lum_vec[0] + g * lum_vec[1] + b * lum_vec[2]; } } - k_e = sum_k_e / (res.x * res.y) as Float; + k_e = sum_k_e / (res.x() * res.y()) as Float; } let side_factor = if two_sided { 2.0 } else { 1.0 }; @@ -185,7 +194,7 @@ impl CreateLight for DiffuseAreaLight { let specific = DiffuseAreaLight::new( render_from_light, medium.into(), - l.as_ref(), + *l_for_scale, scale, shape.upload(arena), alpha.upload(arena), diff --git a/src/lights/distant.rs b/src/lights/distant.rs index 0932b50..3941254 100644 --- a/src/lights/distant.rs +++ b/src/lights/distant.rs @@ -3,7 +3,7 @@ use crate::core::spectrum::spectrum_to_photometric; use crate::core::texture::FloatTexture; use crate::utils::{Arena, FileLoc, ParameterDictionary}; use shared::Float; -use shared::core::geometry::{Point3f, Vector3f, VectorLike}; +use shared::core::geometry::{Point3, Point3f, Vector3f, VectorLike}; use shared::core::light::{Light, LightBase, LightType}; use shared::core::medium::{Medium, MediumInterface}; use shared::core::shape::Shape; @@ -25,12 +25,12 @@ impl CreateDistantLight for DistantLight { render_from_light, MediumInterface::empty(), ); - let lemit = lookup_spectrum(le); + let lemit = Ptr::from(&lookup_spectrum(&le)); Self { base, lemit, scale, - scene_center: Vector3f::default(), + scene_center: Point3f::default(), scene_radius: 0., } } @@ -38,23 +38,23 @@ impl CreateDistantLight for DistantLight { impl CreateLight for DistantLight { fn create( - arena: &mut Arena, + _arena: &mut Arena, render_from_light: Transform, - medium: Medium, + _medium: Medium, parameters: &ParameterDictionary, - loc: &FileLoc, - shape: &Shape, - alpha_text: &FloatTexture, + _loc: &FileLoc, + _shape: &Shape, + _alpha_text: &FloatTexture, colorspace: Option<&RGBColorSpace>, ) -> Result { let l = parameters .get_one_spectrum( "L", - colorspace.unwrap().illuminant, + Some(Spectrum::Dense(colorspace.unwrap().illuminant)), SpectrumType::Illuminant, ) .unwrap(); - let mut scale = parameters.get_one_float("scale", 1); + let mut scale = parameters.get_one_float("scale", 1.); let from = parameters.get_one_point3f("from", Point3f::new(0., 0., 0.)); let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.)); @@ -78,12 +78,10 @@ impl CreateLight for DistantLight { 0., 1., ]; - let t = Transform::from_flat(m); + let t = Transform::from_flat(&m).expect("Could not create transform for DistantLight"); let final_render = render_from_light * t; - scale /= spectrum_to_photometric(l.unwrap()); + scale /= spectrum_to_photometric(l); // Adjust scale to meet target illuminance value - // Like for IBLs we measure illuminance as incident on an upward-facing - // patch. let e_v = parameters.get_one_float("illuminance", -1.); if e_v > 0. { scale *= e_v; diff --git a/src/lights/goniometric.rs b/src/lights/goniometric.rs index a7badf0..b60a148 100644 --- a/src/lights/goniometric.rs +++ b/src/lights/goniometric.rs @@ -1,10 +1,12 @@ +use std::path::Path; + use crate::core::image::{Image, ImageIO}; use crate::core::light::{CreateLight, lookup_spectrum}; use crate::core::spectrum::spectrum_to_photometric; use crate::core::texture::FloatTexture; use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::{Arena, FileLoc, ParameterDictionary, resolve_filename}; -use log::error; +use anyhow::{Result, anyhow}; use shared::core::color::ColorEncoding; use shared::core::geometry::Point2i; use shared::core::image::PixelFormat; @@ -17,7 +19,6 @@ use shared::lights::GoniometricLight; use shared::spectra::RGBColorSpace; use shared::utils::{Ptr, Transform}; use shared::{Float, PI}; -use std::fmt::Error; pub trait CreateGoniometricLight { fn new( @@ -43,15 +44,15 @@ impl CreateGoniometricLight for GoniometricLight { medium_interface, ); - let iemit = lookup_spectrum(le); - let d = image.unwrap().get_sampling_distribution_uniform(); - let distrib = PiecewiseConstant2D::new_with_data(d); + let iemit = lookup_spectrum(&le); + // let d = image.get_sampling_distribution_uniform(); + let distrib = PiecewiseConstant2D::from_image(&image); Self { base, iemit, scale, - image, - distrib, + image: Ptr::from(image.device_image()), + distrib: Ptr::from(&distrib.device), } } } @@ -67,18 +68,19 @@ impl CreateLight for GoniometricLight { alpha_text: &FloatTexture, colorspace: Option<&RGBColorSpace>, ) -> Result { - let i = params.get_one_spectrum( - "I", - colorspace.unwrap().illuminant, - SpectrumType::Illuminant, - ); + let i = params + .get_one_spectrum( + "I", + Some(Spectrum::Dense(colorspace.unwrap().illuminant)), + SpectrumType::Illuminant, + ) + .expect("Could not retrieve spectrum"); let mut scale = params.get_one_float("scale", 1.); - let filename = resolve_filename(params.get_one_string("filename", "")); - let mut image: Option = None; - let image = if filename.is_empty() { - None + let filename = resolve_filename(¶ms.get_one_string("filename", "")); + let image: Ptr = if filename.is_empty() { + Ptr::null() } else { - let im = Image::read(&filename, None) + let im = Image::read(Path::new(&filename), None) .map_err(|e| error!(loc, "could not load image '{}': {}", filename, e))?; let loaded = im.image; @@ -100,23 +102,22 @@ impl CreateLight for GoniometricLight { )); } - Some(convert_to_luminance_image(&loaded, &filename, loc)?) + Ptr::from(&convert_to_luminance_image(&loaded, &filename, loc)?) }; - scale /= spectrum_to_photometric(&i); + scale /= spectrum_to_photometric(i); let phi_v = params.get_one_float("power", -1.0); if phi_v > 0.0 { - if let Some(ref img) = image { - let k_e = compute_emissive_power(image); - scale *= phi_v / k_e; - } + let k_e = compute_emissive_power(&image); + scale *= phi_v / k_e; } let swap_yz: [Float; 16] = [ 1., 0., 0., 0., 0., 0., 1., 0., 0., 1., 0., 0., 0., 0., 0., 1., ]; - let t = Transform::from_flat(swap_yz); + let t = Transform::from_flat(&swap_yz) + .expect("Could not create transform for GoniometricLight"); let final_render_from_light = render_from_light * t; let specific = @@ -126,11 +127,7 @@ impl CreateLight for GoniometricLight { } } -fn convert_to_luminance_image( - image: &Image, - filename: &str, - loc: &FileLoc, -) -> Result { +fn convert_to_luminance_image(image: &Image, filename: &str, loc: &FileLoc) -> Result { let res = image.resolution(); let rgb_desc = image.get_channel_desc(&["R", "G", "B"]); let y_desc = image.get_channel_desc(&["Y"]); @@ -143,10 +140,10 @@ fn convert_to_luminance_image( (Ok(_), Err(_)) => { // Convert RGB to Y (luminance) - let mut y_pixels = Vec::with_capacity((res.x * res.y) as usize); + let mut y_pixels = Vec::with_capacity((res.x() * res.y()) as usize); - for y in 0..res.y { - for x in 0..res.x { + for y in 0..res.y() { + for x in 0..res.x() { let r = image.get_channel(Point2i::new(x, y), 0); let g = image.get_channel(Point2i::new(x, y), 1); let b = image.get_channel(Point2i::new(x, y), 2); @@ -154,17 +151,11 @@ fn convert_to_luminance_image( } } - Ok(Image::from_pixels( - PixelFormat::F32, - res, - &["Y"], - ColorEncoding::Linear, - &y_pixels, - )) + Ok(Image::from_f32(y_pixels, res, &["Y"].to_vec())) } (Err(_), Ok(_)) => { - // Already has Y channel, use as-is + // Already has Y channel Ok(image.clone()) } @@ -179,11 +170,11 @@ fn compute_emissive_power(image: &Image) -> Float { let res = image.resolution(); let mut sum_y = 0.0; - for y in 0..res.y { - for x in 0..res.x { + for y in 0..res.y() { + for x in 0..res.x() { sum_y += image.get_channel(Point2i::new(x, y), 0); } } - 4.0 * PI * sum_y / (res.x * res.y) as Float + 4.0 * PI * sum_y / (res.x() * res.y()) as Float } diff --git a/src/lights/infinite.rs b/src/lights/infinite.rs index cde079b..9e76ff7 100644 --- a/src/lights/infinite.rs +++ b/src/lights/infinite.rs @@ -1,10 +1,15 @@ use crate::Arena; -use crate::core::image::Image; +use crate::core::image::{Image, ImageIO}; use crate::core::spectrum::spectrum_to_photometric; -use crate::utils::{FileLoc, ParameterDictionary, resolve_filename}; -use log::error; +use crate::spectra::{get_colorspace_context, get_spectra_context}; +use crate::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D}; +use crate::utils::{FileLoc, ParameterDictionary, Upload, resolve_filename}; +use anyhow::{Result, anyhow}; + +use rayon::iter::{IndexedParallelIterator, ParallelIterator}; +use rayon::prelude::ParallelSliceMut; use shared::core::camera::CameraTransform; -use shared::core::geometry::{Bounds2f, Frame, Point2f, Point2i, Point3f, cos_theta}; +use shared::core::geometry::{Bounds2f, Frame, Point2f, Point2i, Point3f, VectorLike, cos_theta}; use shared::core::image::{PixelFormat, WrapMode}; use shared::core::light::{Light, LightBase, LightType}; use shared::core::medium::MediumInterface; @@ -14,10 +19,9 @@ use shared::lights::{ImageInfiniteLight, PortalInfiniteLight, UniformInfiniteLig use shared::spectra::RGBColorSpace; use shared::utils::hash::hash_float; use shared::utils::math::{equal_area_sphere_to_square, equal_area_square_to_sphere}; -use shared::utils::sampling::{DevicePiecewiseConstant2D, WindowedPiecewiseConstant2D}; use shared::utils::{Ptr, Transform}; use shared::{Float, PI}; -use std::fmt::Error; +use std::path::Path; use std::sync::Arc; use crate::core::light::lookup_spectrum; @@ -27,8 +31,8 @@ pub trait CreateImageInfiniteLight { render_from_light: Transform, medium_interface: MediumInterface, scale: Float, - image: Ptr, - image_color_space: Ptr, + image: Arc, + image_color_space: Arc, ) -> Self; } @@ -37,13 +41,13 @@ impl CreateImageInfiniteLight for ImageInfiniteLight { render_from_light: Transform, medium_interface: MediumInterface, scale: Float, - image: Ptr, - image_color_space: Ptr, + image: Arc, + image_color_space: Arc, ) -> Self { let base = LightBase::new( LightType::Infinite, - &render_from_light, - &MediumInterface::default(), + render_from_light, + MediumInterface::default(), ); let desc = image @@ -52,52 +56,62 @@ impl CreateImageInfiniteLight for ImageInfiniteLight { assert_eq!(3, desc.size()); assert!(desc.is_identity()); - if image.resolution().x() != image.resolution().y() { - let into = hash_float(hashee); - panic!( - "Image resolution ({}, {}) is non-square. It's unlikely this is an equal area environment map.", - image.resolution.x(), - image.resolution.y() - ); - } - let mut d = image.get_sampling_distribution_uniform(); - let domain = Bounds2f::from_points(Point2f::new(0., 0.), Point2f::new(1., 1.)); - let distrib = DevicePiecewiseConstant2D::new_with_bounds(&d, domain); - let slice = &mut d.values; // or d.as_slice_mut() - let count = slice.len() as Float; - let sum: Float = slice.iter().sum(); - let average = sum / count; + let res = image.resolution(); + assert_eq!( + res.x(), + res.y(), + "Image resolution ({}, {}) is non-square. Unlikely to be an equal area environment map.", + res.x(), + res.y() + ); + + let n_u = res.x() as usize; + let n_v = res.y() as usize; + let mut data: Vec = (0..n_v) + .flat_map(|v| { + (0..n_u).map(move |u| { + image + .get_channels(Point2i::new(u as i32, v as i32)) + .average() + }) + }) + .collect(); + + let distrib = PiecewiseConstant2D::new(&data, n_u, n_v); + + let slice = d.as_mut_slice(); + let average = slice.iter().sum::() / slice.len() as Float; + + let mut all_zero = true; for v in slice.iter_mut() { *v = (*v - average).max(0.0); + all_zero &= *v == 0.0; } - let all_zero = slice.iter().all(|&v| v == 0.0); if all_zero { - for v in slice.iter_mut() { - *v = 1.0; - } + data.fill(1.0); } - let compensated_distrib = DevicePiecewiseConstant2D::new_with_bounds(&d, domain); + let compensated_distrib = PiecewiseConstant2D::new(&data, n_u, n_v); ImageInfiniteLight { base, - image: &image, - image_color_space, + image: Ptr::from(image.device_image()), + image_color_space: Ptr::from(image_color_space.as_ref()), scene_center: Point3f::default(), scene_radius: 0., scale, - distrib: &distrib, - compensated_distrib: &compensated_distrib, - }; + distrib: Ptr::from(&*distrib), + compensated_distrib: Ptr::from(&*compensated_distrib), + } } } #[derive(Debug)] struct InfinitePortalLightStorage { image: Image, - distribution: WindowedPiecewiseConstant2D, + distribution: DeviceWindowedPiecewiseConstant2D, image_color_space: RGBColorSpace, } @@ -112,9 +126,9 @@ pub trait CreatePortalInfiniteLight { fn new( render_from_light: Transform, scale: Float, - image: Ptr, - image_color_space: Ptr, - points: Ptr, + image: Arc, + image_color_space: Arc, + points: Vec, ) -> Self; } @@ -122,14 +136,14 @@ impl CreatePortalInfiniteLight for PortalInfiniteLight { fn new( render_from_light: Transform, scale: Float, - image: Ptr, - image_color_space: Ptr, - points: Ptr, + image: Arc, + image_color_space: Arc, + points: Vec, ) -> Self { let base = LightBase::new( LightType::Infinite, - &render_from_light, - &MediumInterface::default(), + render_from_light, + MediumInterface::default(), ); let desc = image @@ -139,7 +153,7 @@ impl CreatePortalInfiniteLight for PortalInfiniteLight { }); assert_eq!(3, desc.offset.len()); - let src_res = image.resolution; + let src_res = image.resolution(); if src_res.x() != src_res.y() { panic!( "Image resolution ({}, {}) is non-square. It's unlikely this is an equal area environment map.", @@ -205,12 +219,17 @@ impl CreatePortalInfiniteLight for PortalInfiniteLight { c, WrapMode::OctahedralSphere.into(), ); - row_pixels[pixel_idx + c] = val; + row_pixels[pixel_idx + c as usize] = val; } } }); - let img = Image::new(PixelFormat::F32, src_res, &["R", "G", "B"], image.encoding); + let img = Image::new( + PixelFormat::F32, + src_res, + &["R", "G", "B"], + image.encoding().into(), + ); let duv_dw_closure = |p: Point2f| -> Float { let (_, jacobian) = Self::render_from_image(portal_frame, p); @@ -222,12 +241,12 @@ impl CreatePortalInfiniteLight for PortalInfiniteLight { Bounds2f::from_points(Point2f::new(0., 0.), Point2f::new(1., 1.)), ); - let distribution = WindowedPiecewiseConstant2D::new(d); + let distribution = DeviceWindowedPiecewiseConstant2D::new(d); PortalInfiniteLight { base, - image: img, - image_color_space: &image_color_space, + image: Ptr::from(img.device_image()), + image_color_space: Ptr::from(&*image_color_space), scale, scene_center: Point3f::default(), scene_radius: 0., @@ -246,10 +265,10 @@ impl CreateUniformInfiniteLight for UniformInfiniteLight { fn new(render_from_light: Transform, le: Spectrum, scale: Float) -> Self { let base = LightBase::new( LightType::Infinite, - &render_from_light, - &MediumInterface::default(), + render_from_light, + MediumInterface::default(), ); - let lemit = lookup_spectrum(le); + let lemit = Ptr::from(&lookup_spectrum(&le)); Self { base, lemit, @@ -268,11 +287,11 @@ pub fn create( parameters: &ParameterDictionary, colorspace: &RGBColorSpace, loc: &FileLoc, -) -> Result { +) -> Result { let l = parameters.get_spectrum_array("L", SpectrumType::Illuminant); let mut scale = parameters.get_one_float("scale", 1.0); let portal = parameters.get_point3f_array("portal"); - let filename = resolve_filename(parameters.get_one_string("filename", "")); + let filename = resolve_filename(¶meters.get_one_string("filename", "")); let e_v = parameters.get_one_float("illuminance", -1.0); let has_spectrum = !l.is_empty(); @@ -280,22 +299,22 @@ pub fn create( let has_portal = !portal.is_empty(); if has_spectrum && has_file { - return Err(error!(loc, "cannot specify both \"L\" and \"filename\"")); + return Err(anyhow!(loc, "cannot specify both \"L\" and \"filename\"")); } if !has_file && !has_portal { let spectrum = if has_spectrum { - scale /= spectrum_to_photometric(&l[0]); - &l[0] + scale /= spectrum_to_photometric(l[0]); + l[0] } else { - &colorspace.illuminant + Spectrum::Dense(colorspace.illuminant) }; if e_v > 0.0 { scale *= e_v / PI; } - let light = UniformInfiniteLight::new(render_from_light, lookup_spectrum(spectrum), scale); + let light = UniformInfiniteLight::new(render_from_light, spectrum, scale); return Ok(Light::InfiniteUniform(light)); } @@ -303,32 +322,28 @@ pub fn create( let (image, image_cs) = load_image_or_constant(&filename, &l, colorspace, loc)?; - scale /= spectrum_to_photometric(&image_cs.illuminant); + scale /= spectrum_to_photometric(Spectrum::Dense(image_cs.illuminant)); if e_v > 0.0 { let k_e = compute_hemisphere_illuminance(&image, &image_cs); scale *= e_v / k_e; } - let image_ptr = image.upload(arena); - let cs_ptr = image_cs.upload(arena); + // let image_ptr = image.upload(arena); + // let cs_ptr = image_cs.upload(arena); if has_portal { let portal_render: Vec = portal .iter() - .map(|p| camera_transform.render_from_world(*p)) + .map(|p| camera_transform.camera_from_world(0.0).apply_to_point(*p)) .collect(); - let light = PortalInfiniteLight::new( - render_from_light, - scale, - image_ptr, - cs_ptr, - arena.alloc_slice(&portal_render), - ); + let (portal_ptr, portal_len) = arena.alloc_slice(&portal_render); + let light = + PortalInfiniteLight::new(render_from_light, scale, image.into(), cs, portal_render); Ok(Light::InfinitePortal(light)) } else { - let light = ImageInfiniteLight::new(render_from_light, medium, scale, image_ptr, cs_ptr); + let light = ImageInfiniteLight::new(render_from_light, medium, scale, image, cs); Ok(Light::InfiniteImage(light)) } } @@ -338,28 +353,28 @@ fn load_image_or_constant( l: &[Spectrum], colorspace: &RGBColorSpace, loc: &FileLoc, -) -> Result<(Image, RGBColorSpace), Error> { +) -> Result<(Image, RGBColorSpace)> { if filename.is_empty() { - let rgb = &l[0].to_rgb(); - let image = Image::new_constant(Point2i::new(1, 1), &["R", "G", "B"], &rgb); + let stdspec = get_spectra_context(); + let rgb = &l[0].to_rgb(colorspace, &stdspec); + let rgb_values = [rgb.r, rgb.g, rgb.b]; + let image = Image::new_constant(Point2i::new(1, 1), &["R", "G", "B"], &rgb_values); Ok((image, colorspace.clone())) } else { - let im = Image::read(filename, None) - .map_err(|e| error!(loc, "failed to load '{}': {}", filename, e))?; + let im = Image::read(Path::new(&filename), None) + .map_err(|e| anyhow!(loc, "failed to load '{}': {}", filename, e))?; if im.image.has_any_infinite_pixels() || im.image.has_any_nan_pixels() { - return Err(error!(loc, "image '{}' has invalid pixels", filename)); + return Err(anyhow!(loc, "image '{}' has invalid pixels", filename)); } im.image .get_channel_desc(&["R", "G", "B"]) - .map_err(|_| error!(loc, "image '{}' must have R, G, B channels", filename))?; + .map_err(|_| anyhow!(loc, "image '{}' must have R, G, B channels", filename))?; - let cs = im - .metadata - .color_space - .unwrap_or_else(|| colorspace.clone()); - let selected = im.image.select_channels(&["R", "G", "B"])?; + let cs = im.metadata.colorspace.unwrap_or_else(|| colorspace.clone()); + let image_desc = im.image.get_channel_desc(&["R", "G", "B"])?; + let selected = im.image.select_channels(&image_desc); Ok((selected, cs)) } @@ -370,13 +385,13 @@ fn compute_hemisphere_illuminance(image: &Image, cs: &RGBColorSpace) -> Float { let res = image.resolution(); let mut sum = 0.0; - for y in 0..res.y { - let v = (y as Float + 0.5) / res.y as Float; - for x in 0..res.x { - let u = (x as Float + 0.5) / res.x as Float; + for y in 0..res.y() { + let v = (y as Float + 0.5) / res.y() as Float; + for x in 0..res.x() { + let u = (x as Float + 0.5) / res.x() as Float; let w = equal_area_square_to_sphere(Point2f::new(u, v)); - if w.z <= 0.0 { + if w.z() <= 0.0 { continue; } @@ -384,9 +399,9 @@ fn compute_hemisphere_illuminance(image: &Image, cs: &RGBColorSpace) -> Float { let g = image.get_channel(Point2i::new(x, y), 1); let b = image.get_channel(Point2i::new(x, y), 2); - sum += (r * lum[0] + g * lum[1] + b * lum[2]) * cos_theta(&w); + sum += (r * lum[0] + g * lum[1] + b * lum[2]) * cos_theta(w); } } - sum * 2.0 * PI / (res.x * res.y) as Float + sum * 2.0 * PI / (res.x() * res.y()) as Float } diff --git a/src/lights/point.rs b/src/lights/point.rs index f2ce731..930b31a 100644 --- a/src/lights/point.rs +++ b/src/lights/point.rs @@ -35,7 +35,7 @@ impl CreatePointLight for PointLight { render_from_light, medium_interface, ); - let i = lookup_spectrum(le); + let i = Ptr::from(lookup_spectrum(&le)); Self { base, scale, i } } @@ -43,24 +43,24 @@ impl CreatePointLight for PointLight { impl CreateLight for PointLight { fn create( - arena: &mut Arena, + _arena: &mut Arena, render_from_light: Transform, medium: Medium, parameters: &ParameterDictionary, - loc: &FileLoc, - shape: &Shape, - alpha_text: &FloatTexture, + _loc: &FileLoc, + _shape: &Shape, + _alpha: &FloatTexture, colorspace: Option<&RGBColorSpace>, ) -> Result { let l = parameters .get_one_spectrum( "L", - colorspace.unwrap().illuminant, + Some(Spectrum::Dense(colorspace.unwrap().illuminant)), SpectrumType::Illuminant, ) .unwrap(); - let mut scale = parameters.get_one_float("scale", 1); - scale /= spectrum_to_photometric(l.unwrap()); + let mut scale = parameters.get_one_float("scale", 1.); + scale /= spectrum_to_photometric(l); let phi_v = parameters.get_one_float("power", 1.); if phi_v > 0. { let k_e = 4. * PI; diff --git a/src/lights/projection.rs b/src/lights/projection.rs index 1e1044d..f6340f2 100644 --- a/src/lights/projection.rs +++ b/src/lights/projection.rs @@ -4,7 +4,7 @@ use crate::core::spectrum::spectrum_to_photometric; use crate::core::texture::FloatTexture; use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename}; -use log::error; +use anyhow::{Result, anyhow}; use shared::Float; use shared::core::geometry::{ Bounds2f, Point2f, Point2i, Point3f, Vector3f, VectorLike, cos_theta, @@ -12,18 +12,19 @@ use shared::core::geometry::{ use shared::core::light::{Light, LightBase, LightType}; use shared::core::medium::{Medium, MediumInterface}; use shared::core::shape::Shape; +use shared::core::spectrum::Spectrum; use shared::lights::ProjectionLight; use shared::spectra::RGBColorSpace; use shared::utils::math::{radians, square}; use shared::utils::{Ptr, Transform}; -use std::fmt::Error; +use std::path::Path; pub trait CreateProjectionLight { fn new( render_from_light: Transform, medium_interface: MediumInterface, scale: Float, - image: Ptr, + image: Ptr, image_color_space: Ptr, fov: Float, ) -> Self; @@ -34,7 +35,7 @@ impl CreateProjectionLight for ProjectionLight { render_from_light: Transform, medium_interface: MediumInterface, scale: Float, - image: Ptr, + image: Ptr, image_color_space: Ptr, fov: Float, ) -> Self { @@ -54,9 +55,9 @@ impl CreateProjectionLight for ProjectionLight { }; let hither = 1e-3; - let screen_from_light = Transform::perspective(fov.unwrap(), hither, 1e30).unwrap(); + let screen_from_light = Transform::perspective(fov, hither, 1e30).unwrap(); let light_from_screen = screen_from_light.inverse(); - let opposite = (radians(fov.unwrap()) / 2.).tan(); + let opposite = (radians(fov) / 2.).tan(); let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect }; let a = 4. * square(opposite) * aspect_ratio; let dwda = |p: Point2f| { @@ -66,7 +67,7 @@ impl CreateProjectionLight for ProjectionLight { }; let d = image.get_sampling_distribution(dwda, screen_bounds); - let distrib = PiecewiseConstant2D::new_with_bounds(&d, screen_bounds); + let distrib = Ptr::from(&PiecewiseConstant2D::from_image(image).device); Self { base, @@ -93,47 +94,49 @@ impl CreateLight for ProjectionLight { _shape: &Shape, _alpha_text: &FloatTexture, colorspace: Option<&RGBColorSpace>, - ) -> Result { + ) -> Result { let mut scale = parameters.get_one_float("scale", 1.); let power = parameters.get_one_float("power", -1.); let fov = parameters.get_one_float("fov", 90.); - let filename = resolve_filename(parameters.get_one_string("filename", "")); + let filename = resolve_filename(¶meters.get_one_string("filename", "")); if filename.is_empty() { return Err(error!(loc, "must provide filename for projection light")); } - let im = Image::read(&filename, None) + let im = Image::read(Path::new(&filename), None) .map_err(|e| error!(loc, "could not load image '{}': {}", filename, e))?; if im.image.has_any_infinite_pixels() { - return Err(error!( + return Err(anyhow!( loc, - "image '{}' has infinite pixels, not suitable for light", filename + "image '{}' has infinite pixels, not suitable for light", + filename )); } if im.image.has_any_nan_pixels() { - return Err(error!( + return Err(anyhow!( loc, - "image '{}' has NaN pixels, not suitable for light", filename + "image '{}' has NaN pixels, not suitable for light", + filename )); } let channel_desc = im .image .get_channel_desc(&["R", "G", "B"]) - .map_err(|_| error!(loc, "image '{}' must have R, G, B channels", filename))?; + .map_err(|_| anyhow!(loc, "image '{}' must have R, G, B channels", filename))?; let image = im.image.select_channels(&channel_desc); let colorspace = im .metadata .colorspace - .ok_or_else(|| error!(loc, "image '{}' missing colorspace metadata", filename))?; + .ok_or_else(|| anyhow!(loc, "image '{}' missing colorspace metadata", filename))?; - scale /= spectrum_to_photometric(colorspace.illuminant); + scale /= spectrum_to_photometric(Spectrum::Dense(colorspace.illuminant)); if power > 0. { - let k_e = compute_emissive_power(&image, colorspace, fov); + let k_e = compute_emissive_power(&image, &colorspace, fov); } let flip = Transform::scale(1., -1., 1.); diff --git a/src/lights/sampler.rs b/src/lights/sampler.rs index 7d7d9e5..845282f 100644 --- a/src/lights/sampler.rs +++ b/src/lights/sampler.rs @@ -1,13 +1,13 @@ use crate::utils::sampling::AliasTableHost; -use shared::core::light::Light; +use shared::core::light::{Light, LightTrait}; use shared::spectra::{SampledSpectrum, SampledWavelengths}; use shared::utils::sampling::AliasTable; use std::collections::HashMap; use std::sync::Arc; pub struct PowerSamplerHost { - pub lights: Vec, - pub light_to_index: HashMap, + pub lights: Vec>, + pub light_to_index: HashMap, pub alias_table: AliasTableHost, } @@ -37,7 +37,7 @@ impl PowerSamplerHost { light_power.push(phi.average()); } - let alias_table = AliasTable::new(&light_power); + let alias_table = AliasTableHost::new(&light_power); Self { lights: lights_vec, diff --git a/src/lights/spot.rs b/src/lights/spot.rs index eee32b1..9a7abbb 100644 --- a/src/lights/spot.rs +++ b/src/lights/spot.rs @@ -42,7 +42,7 @@ impl CreateSpotLight for SpotLight { MediumInterface::empty(), ); - let iemit = lookup_spectrum(le); + let iemit = Ptr::from(&lookup_spectrum(&le)); Self { base, iemit, @@ -59,15 +59,15 @@ impl CreateLight for SpotLight { render_from_light: Transform, medium: Medium, parameters: &ParameterDictionary, - loc: &FileLoc, - shape: &Shape, - alpha_tex: &FloatTexture, + _loc: &FileLoc, + _shape: &Shape, + _alpha_tex: &FloatTexture, colorspace: Option<&RGBColorSpace>, ) -> Result { let i = parameters .get_one_spectrum( "I", - colorspace.unwrap().illuminant, + Some(Spectrum::Dense(colorspace.unwrap().illuminant)), SpectrumType::Illuminant, ) .expect("No spectrum"); @@ -86,7 +86,7 @@ impl CreateLight for SpotLight { let cos_falloff_end = radians(coneangle).cos(); let cos_falloff_start = radians(coneangle - conedelta).cos(); let k_e = - 2. * PI * ((1. - cos_falloff_start) + (cos_falloff_start - cos_falloff_end) / 2); + 2. * PI * ((1. - cos_falloff_start) + (cos_falloff_start - cos_falloff_end) / 2.); scale *= phi_v / k_e; } diff --git a/src/materials/coated.rs b/src/materials/coated.rs index ef8b391..5c42daa 100644 --- a/src/materials/coated.rs +++ b/src/materials/coated.rs @@ -3,6 +3,7 @@ use crate::core::material::CreateMaterial; use crate::core::texture::SpectrumTexture; use crate::spectra::data::get_named_spectrum; use crate::utils::{Arena, FileLoc, TextureParameterDictionary, Upload, parameters::error_exit}; +use anyhow::Result; use shared::core::material::Material; use shared::core::spectrum::Spectrum; use shared::core::texture::SpectrumType; @@ -10,17 +11,16 @@ use shared::materials::coated::*; use shared::spectra::ConstantSpectrum; use shared::textures::SpectrumConstantTexture; use std::collections::HashMap; -use std::fmt::Error; use std::sync::Arc; impl CreateMaterial for CoatedDiffuseMaterial { fn create( parameters: &TextureParameterDictionary, normal_map: Option>, - named_materials: &HashMap, - loc: &FileLoc, + _named_materials: &HashMap, + _loc: &FileLoc, arena: &mut Arena, - ) -> Result { + ) -> Result { let reflectance = parameters .get_spectrum_texture("reflectance", None, SpectrumType::Albedo) .unwrap_or(Arc::new(SpectrumTexture::Constant( @@ -29,18 +29,18 @@ impl CreateMaterial for CoatedDiffuseMaterial { let u_roughness = parameters .get_float_texture_or_null("uroughness") - .or_else(|| parameters.get_float_texture("roughness", 0.5)); + .unwrap_or_else(|| parameters.get_float_texture("roughness", 0.5)); let v_roughness = parameters .get_float_texture_or_null("vroughness") - .unwap_or_else(|| parameters.get_float("roughness", 0.5)); + .unwrap_or_else(|| parameters.get_float_texture("roughness", 0.5)); let thickness = parameters.get_float_texture("thickness", 0.01); let eta = parameters .get_float_array("eta") .first() - .map(|&v| ConstantSpectrum::new(v)) + .map(|&v| Spectrum::Constant(ConstantSpectrum::new(v))) .or_else(|| parameters.get_one_spectrum("eta", None, SpectrumType::Unbounded)) - .unwrap_or_else(|| ConstantSpectrum::new(1.5)); + .unwrap_or_else(|| Spectrum::Constant(ConstantSpectrum::new(1.5))); let max_depth = parameters.get_one_int("maxdepth", 10); let n_samples = parameters.get_one_int("nsamples", 1); @@ -49,7 +49,7 @@ impl CreateMaterial for CoatedDiffuseMaterial { .get_spectrum_texture("albedo", None, SpectrumType::Albedo) .unwrap_or_else(|| { let default_spectrum = Spectrum::Constant(ConstantSpectrum::new(0.)); - SpectrumTexture::Constant(SpectrumConstantTexture::new(default_spectrum)) + SpectrumTexture::Constant(SpectrumConstantTexture::new(default_spectrum)).into() }); let displacement = parameters.get_float_texture("displacement", 0.); let remap_roughness = parameters.get_one_bool("remaproughness", true); @@ -60,12 +60,12 @@ impl CreateMaterial for CoatedDiffuseMaterial { thickness.upload(arena), albedo.upload(arena), g.upload(arena), - eta, + eta.upload(arena), displacement.upload(arena), normal_map.upload(arena), remap_roughness, - max_depth, - n_samples, + max_depth as u32, + n_samples as u32, ); Ok(Material::CoatedDiffuse(specific)) @@ -76,49 +76,59 @@ impl CreateMaterial for CoatedConductorMaterial { fn create( parameters: &TextureParameterDictionary, normal_map: Option>, - named_materials: &HashMap, + _named_materials: &HashMap, loc: &FileLoc, arena: &mut Arena, - ) -> Result { + ) -> Result { let interface_u_roughness = parameters .get_float_texture_or_null("interface.uroughness") - .r_else(|| parameters.get_float_texture("interface.roughness", 0.)); + .unwrap_or_else(|| parameters.get_float_texture("interface.roughness", 0.)); let interface_v_roughness = parameters - .GetFloatTextureOrNull("interface.vroughness") + .get_float_texture_or_null("interface.vroughness") .unwrap_or_else(|| parameters.get_float_texture("interface.vroughness", 0.)); let thickness = parameters.get_float_texture("interface.thickness", 0.01); - let interface_eta = parameters + let interface_eta: Spectrum = parameters .get_float_array("interface.eta") .first() - .map(|&v| ConstantSpectrum::new(v)) + .map(|&v| Spectrum::Constant(ConstantSpectrum::new(v))) .or_else(|| parameters.get_one_spectrum("interface.eta", None, SpectrumType::Unbounded)) - .unwrap_or_else(|| ConstantSpectrum::new(1.5)); + .unwrap_or_else(|| Spectrum::Constant(ConstantSpectrum::new(1.5))); let conductor_u_roughness = parameters .get_float_texture_or_null("conductor.uroughness") .unwrap_or_else(|| parameters.get_float_texture("conductor.roughness", 0.)); let conductor_v_roughness = parameters - .GetFloatTextureOrNull("conductor.vroughness") + .get_float_texture_or_null("conductor.vroughness") .unwrap_or_else(|| parameters.get_float_texture("conductor.vroughness", 0.)); let reflectance = parameters.get_spectrum_texture_or_null("reflectance", SpectrumType::Albedo); let conductor_eta = parameters.get_spectrum_texture_or_null("conductor.eta", SpectrumType::Unbounded); let k = parameters.get_spectrum_texture_or_null("conductor.k", SpectrumType::Unbounded); - let (conductor_eta, k) = match (reflectance, conductor_eta, k) { + + let (conductor_eta, k) = match (&reflectance, conductor_eta, k) { (Some(_), Some(_), _) | (Some(_), _, Some(_)) => { return Err(error_exit( - loc, + Some(loc), "For the coated conductor material, both \"reflectance\" \ and \"eta\" and \"k\" can't be provided.", )); } - (None, eta, k) => ( - eta.unwrap_or_else(|| { - SpectrumConstantTexture::new(get_named_spectrum("metal-Cu-eta")) - }), - k.unwrap_or_else(|| SpectrumConstantTexture::new(get_named_spectrum("metal-Cu-k"))), - ), - (Some(_), None, None) => (conductor_eta, k), + + (None, eta, k) => { + let final_eta = eta.unwrap_or_else(|| { + let s = get_named_spectrum("metal-Cu-eta").expect("Missing copper spectrum"); + Arc::new(SpectrumTexture::Constant(SpectrumConstantTexture::new(s))) + }); + + let final_k = k.unwrap_or_else(|| { + let s = get_named_spectrum("metal-Cu-k").expect("Missing copper spectrum"); + Arc::new(SpectrumTexture::Constant(SpectrumConstantTexture::new(s))) + }); + + (Some(final_eta), Some(final_k)) + } + + (Some(_), None, None) => (None, None), }; let max_depth = parameters.get_one_int("maxdepth", 10); @@ -127,27 +137,28 @@ impl CreateMaterial for CoatedConductorMaterial { let albedo = parameters .get_spectrum_texture("albedo", None, SpectrumType::Albedo) .unwrap_or_else(|| { - SpectrumConstantTexture::new(Spectrum::Constant(ConstantSpectrum::new(0.))) + let spectrum = Spectrum::Constant(ConstantSpectrum::new(0.)); + SpectrumTexture::Constant(SpectrumConstantTexture::new(spectrum)).into() }); let displacement = parameters.get_float_texture_or_null("displacement"); let remap_roughness = parameters.get_one_bool("remaproughness", true); let material = Self::new( - displacement.upload(arena), normal_map.upload(arena), - interface_u_roughness, - interface_v_roughness, - thickness, - interface_eta, - g, - albedo, - conductor_u_roughness, - conductor_v_roughness, - conductor_eta, - k, - reflectance, + displacement.upload(arena), + interface_u_roughness.upload(arena), + interface_v_roughness.upload(arena), + thickness.upload(arena), + interface_eta.upload(arena), + g.upload(arena), + albedo.upload(arena), + conductor_u_roughness.upload(arena), + conductor_v_roughness.upload(arena), + conductor_eta.upload(arena), + k.upload(arena), + reflectance.upload(arena), + max_depth as u32, + n_samples as u32, remap_roughness, - max_depth, - n_samples, ); arena.alloc(material); Ok(Material::CoatedConductor(material)) diff --git a/src/materials/complex.rs b/src/materials/complex.rs index 14ba3a0..64782a4 100644 --- a/src/materials/complex.rs +++ b/src/materials/complex.rs @@ -2,19 +2,16 @@ use crate::core::image::Image; use crate::core::material::CreateMaterial; use crate::spectra::get_colorspace_context; use crate::utils::{Arena, FileLoc, TextureParameterDictionary, Upload}; -use shared::bxdfs::HairBxDF; -use shared::core::bsdf::BSDF; -use shared::core::bssrdf::BSSRDF; -use shared::core::material::{Material, MaterialEvalContext, MaterialTrait}; +use shared::core::material::Material; use shared::core::spectrum::Spectrum; -use shared::core::texture::{GPUFloatTexture, SpectrumType, TextureEvaluator}; +use shared::core::texture::SpectrumType; use shared::materials::complex::*; use shared::spectra::RGBUnboundedSpectrum; -use shared::spectra::SampledWavelengths; +// use shared::spectra::SampledWavelengths; use shared::textures::SpectrumConstantTexture; -use shared::utils::Ptr; +// use shared::utils::Ptr; +use anyhow::Result; use std::collections::HashMap; -use std::fmt::Error; use std::sync::Arc; impl CreateMaterial for HairMaterial { @@ -24,7 +21,7 @@ impl CreateMaterial for HairMaterial { _named_materials: &HashMap, loc: &FileLoc, arena: &mut Arena, - ) -> Result { + ) -> Result { let sigma_a = parameters.get_spectrum_texture_or_null("sigma_a", SpectrumType::Unbounded); let reflectance = parameters .get_spectrum_texture_or_null("reflectance", SpectrumType::Albedo) @@ -37,17 +34,15 @@ impl CreateMaterial for HairMaterial { let sigma_a = if sigma_a.is_none() && !reflectance.is_none() && !has_melanin { let stdcs = get_colorspace_context(); let default_rgb = HairBxDF::sigma_a_from_concentration(1.3, 0.0, stdcs); - Some(Arc::new(SpectrumConstantTexture::new( - Spectrum::RGBUnbounded(RGBUnboundedSpectrum::new( - reflectance.to_rgb(), - default_rgb, - )), - ))) + let spectrum = + Spectrum::RGBUnbounded(RGBUnboundedSpectrum::new(stdc.srgb, default_rgb)); + let texture = SpectrumTexture::Constant(SpectrumConstantTexture::new(spectrum)); + Some(Arc::new(texture)) } else { sigma_a }; - let eta = parameters.get_flot_texture("eta", 1.55); + let eta = parameters.get_float_texture("eta", 1.55); let beta_m = parameters.get_float_texture("beta_m", 0.3); let beta_n = parameters.get_float_texture("beta_n", 0.3); let alpha = parameters.get_float_texture("alpha", 2.); @@ -66,38 +61,6 @@ impl CreateMaterial for HairMaterial { } } -impl MaterialTrait for MeasuredMaterial { - fn get_bsdf( - &self, - _tex_eval: &T, - _ctx: &MaterialEvalContext, - _lambda: &SampledWavelengths, - ) -> BSDF { - todo!() - } - fn get_bssrdf( - &self, - _tex_eval: &T, - _ctx: &MaterialEvalContext, - _lambda: &SampledWavelengths, - ) -> Option { - todo!() - } - - fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool { - todo!() - } - fn get_normal_map(&self) -> *const Image { - todo!() - } - fn get_displacement(&self) -> Ptr { - todo!() - } - fn has_subsurface_scattering(&self) -> bool { - todo!() - } -} - impl CreateMaterial for SubsurfaceMaterial { fn create( _parameters: &TextureParameterDictionary, @@ -105,7 +68,7 @@ impl CreateMaterial for SubsurfaceMaterial { _named_materials: &HashMap, _loc: &FileLoc, _arena: &mut Arena, - ) -> Result { + ) -> Result { todo!() } } diff --git a/src/materials/mix.rs b/src/materials/mix.rs index 47da5c1..e318c98 100644 --- a/src/materials/mix.rs +++ b/src/materials/mix.rs @@ -1,79 +1,23 @@ +// use crate::core::image::Image; +// use crate::core::material::CreateMaterial; +// use shared::core::bsdf::BSDF; +// use shared::core::bssrdf::BSSRDF; +// use shared::core::material::{Material, MaterialEvalContext, MaterialTrait}; +// use shared::core::texture::{GPUFloatTexture, TextureEvaluator}; +// use shared::spectra::SampledWavelengths; +// use shared::utils::Ptr; use crate::core::image::Image; -use shared::core::bsdf::BSDF; -use shared::core::bssrdf::BSSRDF; -use shared::core::material::{Material, MaterialEvalContext, MaterialTrait}; -use shared::core::texture::{GPUFloatTexture, TextureEvaluator}; -use shared::spectra::SampledWavelengths; -use shared::utils::Ptr; -use shared::utils::hash::hash_float; +use crate::core::material::CreateMaterial; +use shared::materials::MixMaterial; -#[repr(C)] -#[derive(Clone, Copy, Debug)] -pub struct MixMaterial { - pub amount: Ptr, - pub materials: [Ptr; 2], -} - -impl MixMaterial { - pub fn choose_material( - &self, - tex_eval: &T, - ctx: &MaterialEvalContext, - ) -> Option<&Material> { - let amt = tex_eval.evaluate_float(&self.amount, ctx); - - let index = if amt <= 0.0 { - 0 - } else if amt >= 1.0 { - 1 - } else { - let u = hash_float(&(ctx.p, ctx.wo)); - if amt < u { 0 } else { 1 } - }; - - self.materials[index].get() - } -} - -impl MaterialTrait for MixMaterial { - fn get_bsdf( - &self, - tex_eval: &T, - ctx: &MaterialEvalContext, - lambda: &SampledWavelengths, - ) -> BSDF { - if let Some(mat) = self.choose_material(tex_eval, ctx) { - mat.get_bsdf(tex_eval, ctx, lambda) - } else { - BSDF::empty() - } - } - - fn get_bssrdf( - &self, - _tex_eval: &T, - _ctx: &MaterialEvalContext, - _lambda: &SampledWavelengths, - ) -> Option { - None - } - - fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool { - tex_eval.can_evaluate(&[&self.amount], &[]) - } - - fn get_normal_map(&self) -> *const Image { - core::ptr::null() - } - - fn get_displacement(&self) -> Ptr { - panic!( - "MixMaterial::get_displacement() shouldn't be called. \ - Displacement is not supported on Mix materials directly." - ); - } - - fn has_subsurface_scattering(&self) -> bool { - false +impl CreateMaterial for MixMaterial { + fn create( + parameters: &crate::utils::TextureParameterDictionary, + normal_map: Option>, + named_materials: &std::collections::HashMap, + loc: &crate::utils::FileLoc, + arena: &mut crate::Arena, + ) -> Result { + todo!() } } diff --git a/src/samplers/halton.rs b/src/samplers/halton.rs index d86ab03..4ff8753 100644 --- a/src/samplers/halton.rs +++ b/src/samplers/halton.rs @@ -1,18 +1,76 @@ use crate::Arena; use crate::core::sampler::CreateSampler; use crate::utils::{FileLoc, ParameterDictionary}; +use anyhow::{Result, anyhow}; use shared::core::geometry::Point2i; use shared::core::options::get_options; use shared::core::sampler::{HaltonSampler, RandomizeStrategy}; -use std::fmt::Error; + +pub trait CreateHaltonSampler { + fn new( + samples_per_pixel: i32, + full_res: Point2i, + randomize: RandomizeStrategy, + seed: u64, + ) -> Self; +} + +impl CreateHaltonSampler for HaltonSampler { + fn new( + samples_per_pixel: i32, + full_res: Point2i, + randomize: RandomizeStrategy, + seed: u64, + ) -> Self { + let digit_permutations = compute_radical_inverse_permutations(seed); + let mut base_scales = [0u64; 2]; + let mut base_exponents = [0u64; 2]; + let bases = [2, 3]; + let res_coords = [full_res.x(), full_res.y()]; + + for i in 0..2 { + let base = bases[i] as u64; + let mut scale = 1u64; + let mut exp = 0u64; + + let limit = std::cmp::min(res_coords[i], MAX_HALTON_RESOLUTION) as u64; + + while scale < limit { + scale *= base; + exp += 1; + } + + base_scales[i] = scale; + base_exponents[i] = exp; + } + + let mut mult_inverse = [0u64; 2]; + + mult_inverse[0] = + Self::multiplicative_inverse(base_scales[0] as i64, base_scales[0] as i64); + mult_inverse[1] = + Self::multiplicative_inverse(base_scales[1] as i64, base_scales[1] as i64); + + Self { + samples_per_pixel, + randomize, + digit_permutations, + base_scales, + base_exponents, + mult_inverse, + halton_index: 0, + dim: 0, + } + } +} impl CreateSampler for HaltonSampler { fn create( params: &ParameterDictionary, full_res: Point2i, - loc: &FileLoc, - arena: &mut Arena, - ) -> Result { + _loc: &FileLoc, + _arena: &mut Arena, + ) -> Result { let options = get_options(); let nsamp = options .quick_render @@ -29,15 +87,15 @@ impl CreateSampler for HaltonSampler { "fastowen" => RandomizeStrategy::FastOwen, "owen" => RandomizeStrategy::Owen, _ => { - return Err(format!( + return Err(anyhow!( "{}: Unknown randomization strategy for Halton", loc )); } }; - let sampler = HaltonSampler::new(nsamp as u32, full_res, s, seed as u64); - arena.alloc(sampler); + let sampler = HaltonSampler::new(nsamp, full_res, s, seed as u64); + // arena.alloc(sampler); Ok(sampler) } } diff --git a/src/samplers/independent.rs b/src/samplers/independent.rs index 8023e65..64cdc1f 100644 --- a/src/samplers/independent.rs +++ b/src/samplers/independent.rs @@ -1,6 +1,7 @@ use crate::Arena; use crate::core::sampler::CreateSampler; use crate::utils::{FileLoc, ParameterDictionary}; +use anyhow::Result; use shared::core::geometry::Point2i; use shared::core::options::get_options; use shared::core::sampler::IndependentSampler; @@ -12,7 +13,7 @@ impl CreateSampler for IndependentSampler { _full_res: Point2i, _loc: &FileLoc, arena: &mut Arena, - ) -> Result { + ) -> Result { let options = get_options(); let nsamp = options .quick_render @@ -20,7 +21,7 @@ impl CreateSampler for IndependentSampler { .or(options.pixel_samples) .unwrap_or_else(|| params.get_one_int("pixelsamples", 16)); let seed = params.get_one_int("seed", options.seed); - let sampler = Self::new(nsamp as usize, seed as u64); + let sampler = Self::new(nsamp, seed as u64); arena.alloc(sampler); Ok(sampler) } diff --git a/src/samplers/sobol.rs b/src/samplers/sobol.rs index 6ff8856..2bd3340 100644 --- a/src/samplers/sobol.rs +++ b/src/samplers/sobol.rs @@ -1,18 +1,18 @@ use crate::Arena; use crate::core::sampler::CreateSampler; use crate::utils::{FileLoc, ParameterDictionary}; +use anyhow::{Result, anyhow}; use shared::core::geometry::Point2i; use shared::core::options::get_options; use shared::core::sampler::{PaddedSobolSampler, RandomizeStrategy, SobolSampler, ZSobolSampler}; -use std::fmt::Error; impl CreateSampler for SobolSampler { fn create( params: &ParameterDictionary, full_res: Point2i, loc: &FileLoc, - arena: &mut Arena, - ) -> Result { + _arena: &mut Arena, + ) -> Result { let options = get_options(); let nsamp = options .quick_render @@ -26,12 +26,12 @@ impl CreateSampler for SobolSampler { "fastowen" => RandomizeStrategy::FastOwen, "owen" => RandomizeStrategy::Owen, _ => { - return Err(format!("{}: Unknown randomization strategy for Sobol", loc)); + return Err(anyhow!("{}: Unknown randomization strategy for Sobol", loc)); } }; - let sampler = Self::new(nsamp as usize, full_res, s, Some(seed as u64)); - arena.alloc(sampler); + let sampler = Self::new(nsamp, full_res, s, Some(seed as u64)); + // arena.alloc(sampler); Ok(sampler) } } @@ -42,7 +42,7 @@ impl CreateSampler for PaddedSobolSampler { _full_res: Point2i, loc: &FileLoc, arena: &mut Arena, - ) -> Result { + ) -> Result { let options = get_options(); let nsamp = options .quick_render @@ -56,14 +56,14 @@ impl CreateSampler for PaddedSobolSampler { "fastowen" => RandomizeStrategy::FastOwen, "owen" => RandomizeStrategy::Owen, _ => { - return Err(format!( + return Err(anyhow!( "{}: Unknown randomization strategy for ZSobol", loc )); } }; - let sampler = Self::new(nsamp as u32, s, Some(seed as u64)); + let sampler = Self::new(nsamp, s, Some(seed as u64)); arena.alloc(sampler); Ok(sampler) } @@ -74,8 +74,8 @@ impl CreateSampler for ZSobolSampler { params: &ParameterDictionary, full_res: Point2i, loc: &FileLoc, - arena: &mut Arena, - ) -> Result { + _arena: &mut Arena, + ) -> Result { let options = get_options(); let nsamp = options .quick_render @@ -89,14 +89,14 @@ impl CreateSampler for ZSobolSampler { "fastowen" => RandomizeStrategy::FastOwen, "owen" => RandomizeStrategy::Owen, _ => { - return Err(format!( + return Err(anyhow!( "{}: Unknown randomization strategy for ZSobol", loc )); } }; - let sampler = ZSobolSampler::new(nsamp as u32, full_res, s, Some(seed as u64)); + let sampler = ZSobolSampler::new(nsamp, full_res, s, Some(seed as u64)); arena.alloc(sampler); Ok(sampler) } diff --git a/src/samplers/stratified.rs b/src/samplers/stratified.rs index 10c2c9f..1506b7e 100644 --- a/src/samplers/stratified.rs +++ b/src/samplers/stratified.rs @@ -1,18 +1,18 @@ use crate::Arena; use crate::core::sampler::CreateSampler; use crate::utils::{FileLoc, ParameterDictionary}; +use anyhow::Result; use shared::core::geometry::Point2i; use shared::core::options::get_options; use shared::core::sampler::StratifiedSampler; -use std::fmt::Error; impl CreateSampler for StratifiedSampler { fn create( params: &ParameterDictionary, _full_res: Point2i, _loc: &FileLoc, - arena: &mut Arena, - ) -> Result { + _arena: &mut Arena, + ) -> Result { let options = get_options(); let jitter = params.get_one_bool("jitter", true); let (x_samples, y_samples) = if options.quick_render { @@ -29,13 +29,8 @@ impl CreateSampler for StratifiedSampler { ) }; let seed = params.get_one_int("seed", options.seed); - let sampler = Self::new( - x_samples as u32, - y_samples as u32, - Some(seed as u64), - jitter, - ); - arena.aloc(sampler); + let sampler = Self::new(x_samples, y_samples, Some(seed as u64), jitter); + // arena.aloc(sampler); Ok(sampler) } diff --git a/src/shapes/bilinear.rs b/src/shapes/bilinear.rs index 1f3b3ac..a8a7330 100644 --- a/src/shapes/bilinear.rs +++ b/src/shapes/bilinear.rs @@ -1,4 +1,4 @@ -use crate::core::image::Image; +use crate::core::image::{Image, ImageIO}; use crate::core::shape::{ALL_BILINEAR_MESHES, CreateShape}; use crate::core::texture::FloatTexture; use crate::shapes::mesh::BilinearPatchMesh; @@ -93,15 +93,11 @@ impl CreateShape for BilinearPatchShape { "\"emissionfilename\" is currently ignored for bilinear patches if \"uv\" coordinates have been provided--sorry!" ); } else { - match Image::read(&filename) { + match Image::read(Path::new(filename), None) { Ok(mut im) => { - im.flip_y(); - - let domain = Bounds2f::new(Point2f::new(0.0, 0.0), Point2f::new(1.0, 1.0)); - let distribution = im.get_sampling_distribution(); // Assuming this returns Array2D - - image_dist = - Some(PiecewiseConstant2D::new_with_bounds(distribution, domain)); + let img = im.image; + img.flip_y(); + image_dist = Some(PiecewiseConstant2D::from_image(&img)); } Err(e) => { warn!("Failed to load emission image \"{}\": {}", filename, e); @@ -111,7 +107,7 @@ impl CreateShape for BilinearPatchShape { } let host = BilinearPatchMesh::new( - render_from_object, + &render_from_object, reverse_orientation, vertex_indices, p, @@ -121,15 +117,15 @@ impl CreateShape for BilinearPatchShape { ); let host_arc = Arc::new(host); - let mut global_store = ALL_BILINEAR_MESHES.lock().unwrap(); + let mut global_store = ALL_BILINEAR_MESHES.lock(); let mesh_index = global_store.len() as u32; global_store.push(host_arc.clone()); drop(global_store); - let n_patches = host_arc.view.n_patches; - let mesh_ptr = &host_arc.view as *const _; + let n_patches = host_arc.device.n_patches; + let mesh_ptr = Ptr::from(&host_arc.device); let mut shapes = Vec::with_capacity(n_patches as usize); for i in 0..n_patches { - shapes.push(Shape::Bilinear(BilinearPatchShape { + shapes.push(Shape::BilinearPatch(BilinearPatchShape { mesh: mesh_ptr, blp_index: i, area: 0.0, diff --git a/src/shapes/curves.rs b/src/shapes/curves.rs index bf5f86b..b8034d0 100644 --- a/src/shapes/curves.rs +++ b/src/shapes/curves.rs @@ -61,9 +61,9 @@ impl CreateShape for CurveShape { object_from_render: Transform, reverse_orientation: bool, parameters: ParameterDictionary, - float_textures: HashMap, - loc: FileLoc, - arena: &mut Arena, + _float_textures: HashMap, + _loc: FileLoc, + _arena: &mut Arena, ) -> Result, String> { let width = parameters.get_one_float("width", 1.0); let width0 = parameters.get_one_float("width0", width); @@ -85,7 +85,7 @@ impl CreateShape for CurveShape { )); } - let cp = parameters.get_point3f_array("P").unwrap_or_default(); + let cp = parameters.get_point3f_array("P"); let n_segments; if basis == "bezier" { @@ -123,7 +123,7 @@ impl CreateShape for CurveShape { } }; - let mut n = parameters.get_normal3f_array("N").unwrap_or_default(); + let mut n = parameters.get_normal3f_array("N"); if !n.is_empty() { if curve_type != CurveType::Ribbon { warn!("Curve normals are only used with \"ribbon\" type curves. Discarding."); @@ -149,7 +149,7 @@ impl CreateShape for CurveShape { parameters.get_one_int("splitdepth", 3) }; - let mut curves: Vec> = Vec::new(); + let mut curves: Vec = Vec::new(); let mut cp_offset = 0; for seg in 0..n_segments { @@ -194,13 +194,13 @@ impl CreateShape for CurveShape { w0, w1, curve_type, - seg_normals, - split_depth, + seg_normals.expect("Could not determine normals to curve segments"), + split_depth.try_into().unwrap(), ); curves.extend(new_curves); } - arena.alloc(curves); + // arena.alloc(curves); Ok(curves) } } diff --git a/src/shapes/mesh.rs b/src/shapes/mesh.rs index e7212f1..2073697 100644 --- a/src/shapes/mesh.rs +++ b/src/shapes/mesh.rs @@ -3,9 +3,9 @@ use crate::utils::sampling::PiecewiseConstant2D; use anyhow::{Context, Result as AnyResult, bail}; use ply_rs::parser::Parser; use ply_rs::ply::{DefaultElement, Property}; -use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector3f}; -use shared::utils::Transform; +use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector3f, VectorLike}; use shared::utils::mesh::{DeviceBilinearPatchMesh, DeviceTriangleMesh}; +use shared::utils::{Ptr, Transform}; use std::fs::File; use std::ops::Deref; use std::path::Path; @@ -17,38 +17,38 @@ pub struct TriQuadMesh { pub n: Vec, pub uv: Vec, pub face_indices: Vec, - pub tri_indices: Vec, - pub quad_indices: Vec, + pub tri_indices: Vec, + pub quad_indices: Vec, } #[derive(Debug)] struct TriangleMeshStorage { - vertex_indices: Vec, - p: Vec, - n: Vec, - s: Vec, - uv: Vec, - face_indices: Vec, + pub p: Vec, + pub n: Vec, + pub s: Vec, + pub uv: Vec, + pub vertex_indices: Vec, + pub face_indices: Vec, } #[derive(Debug)] struct BilinearMeshStorage { - vertex_indices: Vec, - p: Vec, - n: Vec, - uv: Vec, - image_distribution: Option, + pub vertex_indices: Vec, + pub p: Vec, + pub n: Vec, + pub uv: Vec, + pub image_distribution: Option, } #[derive(Debug)] pub struct TriangleMesh { - _storage: Box, + pub storage: Box, pub device: DeviceTriangleMesh, } #[derive(Debug)] pub struct BilinearPatchMesh { - _storage: Box, + pub storage: Box, pub device: DeviceBilinearPatchMesh, } @@ -72,7 +72,7 @@ impl TriangleMesh { pub fn new( render_from_object: &Transform, reverse_orientation: bool, - vertex_indices: Vec, + vertex_indices: Vec, mut p: Vec, mut n: Vec, mut s: Vec, @@ -84,14 +84,14 @@ impl TriangleMesh { // Transform positions to render space for pt in p.iter_mut() { - *pt = render_from_object.apply_point(*pt); + *pt = render_from_object.apply_to_point(*pt); } // Transform and optionally flip normals if !n.is_empty() { assert_eq!(n_vertices, n.len(), "Normal count mismatch"); for nn in n.iter_mut() { - *nn = render_from_object.apply_normal(*nn); + *nn = render_from_object.apply_to_normal(*nn); if reverse_orientation { *nn = -*nn; } @@ -102,7 +102,7 @@ impl TriangleMesh { if !s.is_empty() { assert_eq!(n_vertices, s.len(), "Tangent count mismatch"); for ss in s.iter_mut() { - *ss = render_from_object.apply_vector(*ss); + *ss = render_from_object.apply_to_vector(*ss); } } @@ -131,52 +131,49 @@ impl TriangleMesh { let device = DeviceTriangleMesh { n_triangles: n_triangles as u32, n_vertices: n_vertices as u32, - vertex_indices: storage.vertex_indices.as_ptr(), - p: storage.p.as_ptr(), + vertex_indices: storage.vertex_indices.as_ptr().into(), + p: storage.p.as_ptr().into(), n: if storage.n.is_empty() { - std::ptr::null() + Ptr::null() } else { - storage.n.as_ptr() + storage.n.as_ptr().into() }, s: if storage.s.is_empty() { - std::ptr::null() + Ptr::null() } else { - storage.s.as_ptr() + storage.s.as_ptr().into() }, uv: if storage.uv.is_empty() { - std::ptr::null() + Ptr::null() } else { - storage.uv.as_ptr() + storage.uv.as_ptr().into() }, face_indices: if storage.face_indices.is_empty() { - std::ptr::null() + Ptr::null() } else { - storage.face_indices.as_ptr() + storage.face_indices.as_ptr().into() }, reverse_orientation, transform_swaps_handedness, }; - Self { - _storage: storage, - device, - } + Self { storage, device } } pub fn positions(&self) -> &[Point3f] { - &self._storage.p + &self.storage.p } - pub fn indices(&self) -> &[u32] { - &self._storage.vertex_indices + pub fn indices(&self) -> &[i32] { + &self.storage.vertex_indices } pub fn normals(&self) -> &[Normal3f] { - &self._storage.n + &self.storage.n } pub fn uvs(&self) -> &[Point2f] { - &self._storage.uv + &self.storage.uv } } @@ -184,7 +181,7 @@ impl BilinearPatchMesh { pub fn new( render_from_object: &Transform, reverse_orientation: bool, - vertex_indices: Vec, + vertex_indices: Vec, mut p: Vec, mut n: Vec, uv: Vec, @@ -194,13 +191,13 @@ impl BilinearPatchMesh { let n_vertices = p.len(); for pt in p.iter_mut() { - *pt = render_from_object.apply_point(*pt); + *pt = render_from_object.apply_to_point(*pt); } if !n.is_empty() { assert_eq!(n_vertices, n.len(), "Normal count mismatch"); for nn in n.iter_mut() { - *nn = render_from_object.apply_normal(*nn); + *nn = render_from_object.apply_to_normal(*nn); if reverse_orientation { *nn = -*nn; } @@ -224,31 +221,28 @@ impl BilinearPatchMesh { let device = DeviceBilinearPatchMesh { n_patches: n_patches as u32, n_vertices: n_vertices as u32, - vertex_indices: storage.vertex_indices.as_ptr(), - p: storage.p.as_ptr(), + vertex_indices: storage.vertex_indices.as_ptr().into(), + p: storage.p.as_ptr().into(), n: if storage.n.is_empty() { - std::ptr::null() + Ptr::null() } else { - storage.n.as_ptr() + storage.n.as_ptr().into() }, uv: if storage.uv.is_empty() { - std::ptr::null() + Ptr::null() } else { - storage.uv.as_ptr() + storage.uv.as_ptr().into() }, reverse_orientation, transform_swaps_handedness, image_distribution: storage .image_distribution .as_ref() - .map(|d| &d.device as *const _) - .unwrap_or(std::ptr::null()), + .map(|d| Ptr::from(&d.device)) + .unwrap_or(Ptr::null()), }; - Self { - _storage: storage, - device, - } + Self { storage, device } } } @@ -374,8 +368,8 @@ impl TriQuadMesh { if let Ok(indices) = get_list_uint(f_elem, "vertex_indices") { match indices.len() { - 3 => mesh.tri_indices.extend_from_slice(&indices), - 4 => mesh.quad_indices.extend_from_slice(&indices), + 3 => mesh.tri_indices.extend(indices.iter().map(|&i| i as i32)), + 4 => mesh.quad_indices.extend(indices.iter().map(|&i| i as i32)), n => { log::warn!( "{}: Skipping face with {} vertices (only 3 or 4 supported)", @@ -393,7 +387,7 @@ impl TriQuadMesh { } // Validate indices - let vertex_count = mesh.p.len() as u32; + let vertex_count = mesh.p.len() as i32; for &idx in &mesh.tri_indices { if idx >= vertex_count { bail!( @@ -457,7 +451,7 @@ impl TriQuadMesh { let v10 = p1 - p0; let v20 = p2 - p0; - let face_normal = v10.cross(&v20); + let face_normal = v10.cross(v20); // Accumulate (will normalize later) self.n[i0] = self.n[i0] + Normal3f::from(face_normal); @@ -475,7 +469,7 @@ impl TriQuadMesh { let v10 = p1 - p0; let v20 = p2 - p0; - let face_normal = v10.cross(&v20); + let face_normal = v10.cross(v20); for &idx in &indices { self.n[idx] = self.n[idx] + Normal3f::from(face_normal); @@ -484,7 +478,7 @@ impl TriQuadMesh { // Normalize all normals for normal in &mut self.n { - let len_sq = normal.length_squared(); + let len_sq = normal.norm_squared(); if len_sq > 0.0 { *normal = *normal / len_sq.sqrt(); } diff --git a/src/shapes/triangle.rs b/src/shapes/triangle.rs index 44f7d84..d4f0616 100644 --- a/src/shapes/triangle.rs +++ b/src/shapes/triangle.rs @@ -73,7 +73,7 @@ impl CreateShape for TriangleShape { } } - let mut face_indices = parameters.get_int_array("faceIndices").unwrap_or_default(); + let mut face_indices = parameters.get_int_array("faceIndices"); let n_triangles = vertex_indices.len() / 3; if !face_indices.is_empty() && face_indices.len() != n_triangles { @@ -86,28 +86,28 @@ impl CreateShape for TriangleShape { } let host = TriangleMesh::new( - render_from_object, + &render_from_object, reverse_orientation, vertex_indices, p, - s, n, + s, uvs, face_indices, ); let host_arc = Arc::new(host); - let mut global_store = ALL_TRIANGLE_MESHES.lock().unwrap(); + let mut global_store = ALL_TRIANGLE_MESHES.lock(); let mesh_index = global_store.len() as u32; global_store.push(host_arc.clone()); drop(global_store); - let n_patches = host_arc.view.n_patches; - let mesh_ptr = &host_arc.view as *const _; + let n_patches = host_arc.device.n_triangles; + let mesh_ptr = Ptr::from(&host_arc.device); let mut shapes = Vec::with_capacity(n_patches as usize); for i in 0..n_patches { shapes.push(Shape::Triangle(TriangleShape { mesh: mesh_ptr, - tri_index: i, + tri_index: i as i32, })); } diff --git a/src/spectra/colorspace.rs b/src/spectra/colorspace.rs index 40861b7..beca3b4 100644 --- a/src/spectra/colorspace.rs +++ b/src/spectra/colorspace.rs @@ -5,11 +5,19 @@ use shared::spectra::RGBColorSpace; use shared::utils::math::SquareMatrix; use shared::utils::ptr::Ptr; +#[derive(Clone, Debug)] pub struct RGBColorSpaceData { _illuminant: DenselySampledSpectrumBuffer, pub view: RGBColorSpace, } +impl std::ops::Deref for RGBColorSpaceData { + type Target = RGBColorSpace; + fn deref(&self) -> &Self::Target { + &self.view + } +} + impl RGBColorSpaceData { pub fn new( r: Point2f, @@ -18,7 +26,7 @@ impl RGBColorSpaceData { illuminant: DenselySampledSpectrumBuffer, rgb_to_spectrum_table: Ptr, ) -> Self { - let w_xyz: XYZ = illuminant.to_xyz(); + let w_xyz: XYZ = Spectrum::Dense(illuminant).to_xyz(); let w = w_xyz.xy(); let r_xyz = XYZ::from_xyy(r, Some(1.0)); let g_xyz = XYZ::from_xyy(g, Some(1.0)); @@ -29,7 +37,8 @@ impl RGBColorSpaceData { [r_xyz.z(), g_xyz.z(), b_xyz.z()], ]; let rgb = SquareMatrix::new(rgb_values); - let c: RGB = rgb.inverse()? * w_xyz; + let c_vec: Vector3f = rgb.inverse().unwrap() * w_xyz; + let c = RGB::from(c); let xyz_from_rgb = rgb * SquareMatrix::diag(&[c.r, c.g, c.b]); let rgb_from_xyz = xyz_from_rgb .inverse() @@ -39,7 +48,7 @@ impl RGBColorSpaceData { g, b, w, - illuminant: illuminant.view, + illuminant: *illuminant, xyz_from_rgb, rgb_from_xyz, rgb_to_spectrum_table, diff --git a/src/spectra/data.rs b/src/spectra/data.rs index 8a7c2bf..dabfa22 100644 --- a/src/spectra/data.rs +++ b/src/spectra/data.rs @@ -6,10 +6,10 @@ use shared::spectra::cie::*; use std::collections::HashMap; use std::sync::LazyLock; -pub fn create_cie_buffer(data: &[Float]) -> Spectrum { - let buffer = PiecewiseLinearSpectrum::from_interleaved(data, false); - let spec = Spectrum::Piecewise(buffer.view); - DenselySampledSpectrumBuffer::from_spectrum(spec) +pub fn create_cie_buffer(data: &[Float]) -> DenselySampledSpectrumBuffer { + let buffer = PiecewiseLinearSpectrumBuffer::from_interleaved(data, false); + let spec = Spectrum::Piecewise(buffer.device); + DenselySampledSpectrumBuffer::from_spectrum(&spec) } pub static NAMED_SPECTRA: LazyLock> = LazyLock::new(|| { @@ -18,7 +18,8 @@ pub static NAMED_SPECTRA: LazyLock> = LazyLock::new(|| macro_rules! add { ($name:expr, $data:expr, $norm:expr) => { let buffer = PiecewiseLinearSpectrumBuffer::from_interleaved($data, $norm); - m.insert($name.to_string(), buffer); + let spectrum = Spectrum::Piecewise(*buffer); + m.insert($name.to_string(), spectrum); }; } @@ -153,6 +154,5 @@ pub static NAMED_SPECTRA: LazyLock> = LazyLock::new(|| }); pub fn get_named_spectrum(name: &str) -> Option { - let buffer = NAMED_SPECTRA.get(name)?; - Some(Spectrum::PiecewiseLinear(buffer.view)) + NAMED_SPECTRA.get(name).copied() } diff --git a/src/spectra/dense.rs b/src/spectra/dense.rs index 76d6bb4..d545c7d 100644 --- a/src/spectra/dense.rs +++ b/src/spectra/dense.rs @@ -1,12 +1,12 @@ use shared::Float; -use shared::core::spectrum::Spectrum; +use shared::core::spectrum::{Spectrum, SpectrumTrait}; use shared::spectra::cie::{CIE_S_LAMBDA, CIE_S0, CIE_S1, CIE_S2, N_CIES}; use shared::spectra::{ BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, PiecewiseLinearSpectrum, - SampledSpectrum, SampledWavelengths, }; use shared::utils::math::square; +#[derive(Clone, Debug)] pub struct DenselySampledSpectrumBuffer { pub device: DenselySampledSpectrum, pub _storage: Vec, @@ -24,7 +24,7 @@ impl DenselySampledSpectrumBuffer { let view = DenselySampledSpectrum { lambda_min, lambda_max, - values: values.as_ptr(), + values: values.as_ptr().into(), }; Self { device: view, @@ -90,8 +90,8 @@ impl DenselySampledSpectrumBuffer { .collect(); let temp_pls = PiecewiseLinearSpectrum { - lambdas: CIE_S_LAMBDA.as_ptr(), - values: coarse_values.as_ptr(), + lambdas: CIE_S_LAMBDA.as_ptr().into(), + values: coarse_values.as_ptr().into(), count: N_CIES as u32, }; @@ -100,9 +100,9 @@ impl DenselySampledSpectrumBuffer { pub fn as_view(&self) -> DenselySampledSpectrum { DenselySampledSpectrum { - lambda_min: self.lambda_min, - lambda_max: self.lambda_max, - values: self.samples.as_ptr(), + lambda_min: self.device.lambda_min, + lambda_max: self.device.lambda_max, + values: self._storage.as_ptr().into(), } } diff --git a/src/spectra/mod.rs b/src/spectra/mod.rs index 5ee38f7..a021797 100644 --- a/src/spectra/mod.rs +++ b/src/spectra/mod.rs @@ -1,10 +1,11 @@ +use crate::globals::{ACES_TABLE, DCI_P3_TABLE, REC2020_TABLE, SRGB_TABLE}; use crate::spectra::colorspace::RGBColorSpaceData; use shared::core::color::RGBToSpectrumTable; use shared::core::geometry::Point2f; use shared::core::spectrum::Spectrum; use shared::core::spectrum::StandardSpectra; +use shared::spectra::DeviceStandardColorSpaces; use shared::spectra::RGBColorSpace; -use shared::spectra::StandardColorSpaces; use shared::spectra::cie::{CIE_D65, CIE_X, CIE_Y, CIE_Z}; use std::sync::Arc; use std::sync::LazyLock; @@ -14,9 +15,16 @@ pub mod data; pub mod dense; pub mod piecewise; -// pub use data; pub use dense::DenselySampledSpectrumBuffer; +fn get_d65_illuminant_buffer() -> DenselySampledSpectrumBuffer { + DenselySampledSpectrumBuffer::new( + CIE_D65_DATA.device.lambda_min, + CIE_D65_DATA.device.lambda_max, + CIE_D65_DATA._storage.clone(), + ) +} + static CIE_X_DATA: LazyLock = LazyLock::new(|| data::create_cie_buffer(&CIE_X)); static CIE_Y_DATA: LazyLock = @@ -27,92 +35,86 @@ static CIE_D65_DATA: LazyLock = LazyLock::new(|| data::create_cie_buffer(&CIE_D65)); pub fn cie_x() -> Spectrum { - Spectrum::DenselySampled(CIE_X_DATA.view) + Spectrum::Dense(CIE_X_DATA.device) } pub fn cie_y() -> Spectrum { - Spectrum::DenselySampled(CIE_Y_DATA.view) + Spectrum::Dense(CIE_Y_DATA.device) } pub fn cie_z() -> Spectrum { - Spectrum::DenselySampled(CIE_Z_DATA.view) + Spectrum::Dense(CIE_Z_DATA.device) } pub fn cie_d65() -> Spectrum { - Spectrum::DenselySampled(CIE_D65_DATA.view) + Spectrum::Dense(CIE_D65_DATA.device) } pub fn get_spectra_context() -> StandardSpectra { StandardSpectra { - x: CIE_X_DATA.view, - y: CIE_Y_DATA.view, - z: CIE_Z_DATA.view, - d65: CIE_D65_DATA.view, + x: CIE_X_DATA.device, + y: CIE_Y_DATA.device, + z: CIE_Z_DATA.device, + d65: CIE_D65_DATA.device, } } -pub static SRGB: LazyLock = LazyLock::new(|| { - let illum = DenselySampledSpectrumBuffer::new( - CIE_D65_DATA.view.lambda_min, - CIE_D65_DATA.view.lambda_max, - CIE_D65_DATA._storage.clone(), - ); +pub static SRGB: LazyLock> = LazyLock::new(|| { + let illum = get_d65_illuminant_buffer(); + let r = Point2f::new(0.64, 0.33); + let g = Point2f::new(0.3, 0.6); + let b = Point2f::new(0.15, 0.06); + let table = Ptr::from(SRGB_TABLE.clone()); - RGBColorSpaceData::new( - Point2f::new(0.64, 0.33), - Point2f::new(0.3, 0.6), - Point2f::new(0.15, 0.06), - illum, - RGBToSpectrumTable::srgb(), - ) + Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr)) }); -pub static DCI_P3: LazyLock = LazyLock::new(|| { - let illum = DenselySampledSpectrumBuffer::new( - CIE_D65_DATA.view.lambda_min, - CIE_D65_DATA.view.lambda_max, - CIE_D65_DATA._storage.clone(), - ); - +pub static DCI_P3: LazyLock> = LazyLock::new(|| { + let illum = get_d65_illuminant_buffer(); let r = Point2f::new(0.680, 0.320); let g = Point2f::new(0.265, 0.690); let b = Point2f::new(0.150, 0.060); - RGBColorSpaceData::new(r, g, b, illum, RGBToSpectrumTable) + let table_ptr = Ptr::from(DCI_P3_TABLE.clone()); + + Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr)) }); -pub static REC2020: LazyLock> = LazyLock::new(|| { - let illum = DenselySampledSpectrumBuffer::new( - CIE_D65_DATA.view.lambda_min, - CIE_D65_DATA.view.lambda_max, - CIE_D65_DATA._storage.clone(), - ); - +pub static REC2020: LazyLock> = LazyLock::new(|| { + let illum = get_d65_illuminant_buffer(); let r = Point2f::new(0.708, 0.292); let g = Point2f::new(0.170, 0.797); let b = Point2f::new(0.131, 0.046); - let table = RGBToSpectrumTable::rec2020(); + let table_ptr = Ptr::from(REC2020_TABLE.clone()); - RGBColorSpace::new(r, g, b, illum, table) + Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr)) }); -pub static ACES: LazyLock> = LazyLock::new(|| { +pub static ACES: LazyLock> = LazyLock::new(|| { + let illum = get_d65_illuminant_buffer(); let r = Point2f::new(0.7347, 0.2653); let g = Point2f::new(0.0000, 1.0000); let b = Point2f::new(0.0001, -0.0770); - let illuminant = Spectrum::std_illuminant_d65(); - let table = RGBToSpectrumTable::aces2065_1(); + let table_ptr = Ptr::from(ACES_TABLE.clone()); - RGBColorSpaceData::new(r, g, b, illuminant, table) + Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr)) }); +#[derive(Debug, Clone)] +pub struct StandardColorSpaces { + pub srgb: Arc, + pub dci_p3: Arc, + pub rec2020: Arc, + pub aces2065_1: Arc, +} + pub fn get_colorspace_context() -> StandardColorSpaces { StandardColorSpaces { - srgb: SRGB, - dci_p3: DCI_P3, - rec2020: REC2020, - aces2065_1: ACES, + srgb: SRGB.clone().into(), + dci_p3: DCI_P3.clone().into(), + rec2020: REC2020.clone().into(), + aces2065_1: ACES.clone().into(), } } diff --git a/src/spectra/piecewise.rs b/src/spectra/piecewise.rs index 8609f18..e58cb34 100644 --- a/src/spectra/piecewise.rs +++ b/src/spectra/piecewise.rs @@ -1,11 +1,11 @@ use crate::utils::read_float_file; use shared::Float; use shared::spectra::PiecewiseLinearSpectrum; +use std::cmp::Ordering; use std::ops::Deref; -use std::sync::atomic::Ordering; pub struct PiecewiseLinearSpectrumBuffer { - pub view: PiecewiseLinearSpectrum, + pub device: PiecewiseLinearSpectrum, _lambdas: Vec, _values: Vec, } @@ -13,7 +13,7 @@ pub struct PiecewiseLinearSpectrumBuffer { impl Deref for PiecewiseLinearSpectrumBuffer { type Target = PiecewiseLinearSpectrum; fn deref(&self) -> &Self::Target { - &self.view + &self.device } } @@ -21,12 +21,12 @@ impl PiecewiseLinearSpectrumBuffer { pub fn new(lambdas: Vec, values: Vec) -> Self { assert_eq!(lambdas.len(), values.len()); let view = PiecewiseLinearSpectrum { - lambdas: lambdas.as_ptr(), - values: values.as_ptr(), + lambdas: lambdas.as_ptr().into(), + values: values.as_ptr().into(), count: lambdas.len() as u32, }; Self { - view, + device: view, _lambdas: lambdas, _values: values, } diff --git a/src/textures/image.rs b/src/textures/image.rs index b7a6819..565485e 100644 --- a/src/textures/image.rs +++ b/src/textures/image.rs @@ -6,6 +6,7 @@ use shared::core::color::ColorEncoding; use shared::core::color::RGB; use shared::core::geometry::Vector2f; use shared::core::image::WrapMode; +use shared::core::spectrum::SpectrumTrait; use shared::core::texture::{SpectrumType, TextureEvalContext, TextureMapping2D}; use shared::spectra::{ RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, @@ -129,7 +130,7 @@ impl SpectrumTextureTrait for SpectrumImageTexture { let dst0 = Vector2f::new(c.dsdx, c.dtdx); let dst1 = Vector2f::new(c.dsdy, c.dtdy); let rgb_unclamp = self.base.scale * self.base.mipmap.filter::(c.st, dst0, dst1); - let rgb = RGB::clamp_zero(rgb_unclamp); + let rgb = RGB::clamp_zero(&rgb_unclamp); if let Some(cs) = self.base.mipmap.get_rgb_colorspace() { match self.spectrum_type { SpectrumType::Unbounded => { diff --git a/src/textures/mix.rs b/src/textures/mix.rs index 20453bb..9ec4c30 100644 --- a/src/textures/mix.rs +++ b/src/textures/mix.rs @@ -2,7 +2,7 @@ use crate::core::texture::FloatTexture; use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait}; use crate::utils::{Arena, FileLoc, TextureParameterDictionary}; use shared::Float; -use shared::core::geometry::Vector3f; +use shared::core::geometry::{Vector3f, VectorLike}; use shared::core::texture::TextureEvalContext; use shared::spectra::{SampledSpectrum, SampledWavelengths}; use shared::utils::Transform; @@ -28,12 +28,13 @@ impl FloatMixTexture { _render_from_texture: &Transform, params: &TextureParameterDictionary, _loc: &FileLoc, - arena: &mut Arena, - ) -> Self { + _arena: &mut Arena, + ) -> FloatTexture { let tex1 = params.get_float_texture("tex1", 0.); let tex2 = params.get_float_texture("tex2", 1.); let amount = params.get_float_texture("amount", 0.5); - arena.alloc(Self::new(tex1, tex2, amount)) + // arena.alloc(Self::new(tex1, tex2, amount)); + FloatTexture::Mix(Self::new(tex1, tex2, amount)) } } @@ -68,13 +69,14 @@ impl FloatDirectionMixTexture { render_from_texture: &Transform, params: &TextureParameterDictionary, _loc: &FileLoc, - arena: &mut Arena, - ) -> Self { + _arena: &mut Arena, + ) -> FloatTexture { let dir_raw = params.get_one_vector3f("dir", Vector3f::new(0., 1., 0.)); let dir = render_from_texture.apply_to_vector(dir_raw).normalize(); let tex1 = params.get_float_texture("tex1", 0.); let tex2 = params.get_float_texture("tex2", 1.); - arena.alloc(Self::new(tex1, tex2, dir)) + // arena.alloc(Self::new(tex1, tex2, dir)) + FloatTexture::DirectionMix(Self::new(tex1, tex2, dir)) } } @@ -85,7 +87,12 @@ impl FloatTextureTrait for FloatDirectionMixTexture { } #[derive(Debug, Clone)] -pub struct SpectrumMixTexture; +pub struct SpectrumMixTexture { + pub tex1: Arc, + pub tex2: Arc, + pub amount: Arc, +} + impl SpectrumTextureTrait for SpectrumMixTexture { fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { todo!() @@ -93,7 +100,12 @@ impl SpectrumTextureTrait for SpectrumMixTexture { } #[derive(Debug, Clone)] -pub struct SpectrumDirectionMixTexture; +pub struct SpectrumDirectionMixTexture { + pub tex1: Arc, + pub tex2: Arc, + pub dir: Vector3f, +} + impl SpectrumTextureTrait for SpectrumDirectionMixTexture { fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { todo!() diff --git a/src/textures/ptex.rs b/src/textures/ptex.rs index 19ec98c..a89bf6f 100644 --- a/src/textures/ptex.rs +++ b/src/textures/ptex.rs @@ -1,30 +1,14 @@ // use crate::utils::{Arena, FileLoc, TextureParameterDictionary}; use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait}; -use ptex::Cache; -use ptex_filter::{PtexFilterOptions, PtexFilterType, ptex_filter_create}; +use crate::spectra::get_colorspace_context; +use shared::core::spectrum::SpectrumTrait; +// use ptex::Cache; +// use ptex_filter::{PtexFilter, PtexFilterOptions, PtexFilterType}; + use shared::Float; use shared::core::color::{ColorEncoding, RGB}; use shared::core::texture::{SpectrumType, TextureEvalContext}; -use shared::spectra::{SampledSpectrum, SampledWavelengths}; -use std::sync::OnceLock; - -struct PtexCache(Cache); -unsafe impl Sync for PtexCache {} -unsafe impl Send for PtexCache {} - -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; - let cache = Cache::new(max_files, max_mem, premultiply); - PtexCache(cache) - }) - .0 -} +use shared::spectra::{RGBIlluminantSpectrum, SampledSpectrum, SampledWavelengths}; #[derive(Debug, Clone)] pub struct PtexTextureBase { @@ -36,106 +20,21 @@ pub struct PtexTextureBase { impl PtexTextureBase { pub fn new(filename: String, encoding: ColorEncoding, scale: Float) -> Self { - let cache: &mut 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 - ); - } + log::warn!( + "Ptex support is currently disabled. Texture '{}' will render as Magenta.", + filename + ); Self { filename, encoding, scale, - valid, + valid: false, } } - pub fn sample_texture(&self, 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.0; 4]; - unsafe { - let opts = PtexFilterOptions { - filter: PtexFilterType::BSpline, - lerp: 1, - sharpness: 0.0, - noedgeblend: 0, - // __structSize: std::mem::size_of::() as i32, - }; - - let filter_ptr = ptex_filter_create(texture.as_ptr(), &opts); - if filter_ptr.is_null() { - return None; - } - - // Evaluate - (*filter_ptr).eval( - result.as_mut_ptr(), - 0, - texture.num_channels(), - ctx.face_index as i32, - ctx.uv[0], - ctx.uv[1], - ctx.dudx, - ctx.dvdx, - ctx.dudy, - ctx.dvdy, - ); - - // 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) + pub fn sample_texture(&self, _ctx: &TextureEvalContext) -> Option { + Some(RGB::new(1.0, 0.0, 1.0) * self.scale) } } @@ -145,8 +44,12 @@ pub struct FloatPtexTexture { } impl FloatTextureTrait for FloatPtexTexture { - fn evaluate(&self, _ctx: &TextureEvalContext) -> Float { - todo!() + fn evaluate(&self, ctx: &TextureEvalContext) -> Float { + if let Some(rgb) = self.base.sample_texture(ctx) { + rgb.g + } else { + 0.0 + } } } @@ -157,7 +60,15 @@ pub struct SpectrumPtexTexture { } impl SpectrumTextureTrait for SpectrumPtexTexture { - fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { - todo!() + fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum { + if let Some(rgb) = self.base.sample_texture(ctx) { + let stdcs = get_colorspace_context(); + let srgb = stdcs.srgb.as_ref(); + RGBIlluminantSpectrum::new(srgb, rgb).sample(lambda) + } else { + SampledSpectrum::new(0.0) + } } } + +pub trait CreateGPUPtexTexture {} diff --git a/src/textures/scaled.rs b/src/textures/scaled.rs index 81dc8e9..0c5bffd 100644 --- a/src/textures/scaled.rs +++ b/src/textures/scaled.rs @@ -22,7 +22,7 @@ impl FloatScaledTexture { _render_from_texture: &Transform, params: &TextureParameterDictionary, _loc: &FileLoc, - arena: &mut Arena, + _arena: &mut Arena, ) -> FloatTexture { let mut tex = params.get_float_texture("tex", 1.); let mut scale = params.get_float_texture("scale", 1.); @@ -42,8 +42,8 @@ impl FloatScaledTexture { std::mem::swap(&mut tex, &mut scale); } std::mem::swap(&mut tex, &mut scale); - arena.alloc(FloatScaledTexture::new(tex, scale)); - FloatTexture::FloatScaled(FloatScaledTexture::new(tex, scale)) + // arena.alloc(FloatScaledTexture::new(tex, scale)); + FloatTexture::Scaled(FloatScaledTexture::new(tex.clone(), scale.clone())) } } diff --git a/src/utils/arena.rs b/src/utils/arena.rs index a404ff1..ecd116b 100644 --- a/src/utils/arena.rs +++ b/src/utils/arena.rs @@ -1,27 +1,34 @@ use crate::core::image::Image; -use crate::core::texture::{FloatTexture, SpectrumTexture}; +use crate::core::texture::{FloatTexture, SpectrumTexture, get_texture_cache}; use crate::shapes::{BilinearPatchMesh, TriangleMesh}; +use crate::textures::CreateGPUSpectrumPtex; use crate::utils::sampling::PiecewiseConstant2D; use shared::core::color::RGBToSpectrumTable; use shared::core::image::DeviceImage; use shared::core::shape::Shape; use shared::core::spectrum::Spectrum; use shared::core::texture::{GPUFloatTexture, GPUSpectrumTexture}; -use shared::spectra::{RGBColorSpace, StandardColorSpaces}; +use shared::spectra::{DeviceStandardColorSpaces, RGBColorSpace}; use shared::textures::*; use shared::utils::Ptr; use shared::utils::mesh::{DeviceBilinearPatchMesh, DeviceTriangleMesh}; use shared::utils::sampling::{DevicePiecewiseConstant1D, DevicePiecewiseConstant2D}; use std::alloc::Layout; +use std::collections::HashMap; +use std::slice::from_raw_parts; use std::sync::Arc; pub struct Arena { buffer: Vec<(*mut u8, Layout)>, + texture_cache: HashMap, } impl Arena { pub fn new() -> Self { - Self { buffer: Vec::new() } + Self { + buffer: Vec::new(), + texture_cache: HashMap::new(), + } } pub fn alloc(&mut self, value: T) -> Ptr { @@ -71,25 +78,53 @@ impl Arena { panic!("cudaMallocManaged failed: {:?}", result); } - self.allocations.push((ptr as *mut u8, layout)); + self.buffer.push((ptr as *mut u8, layout)); ptr as *mut u8 } + pub fn get_texture_object(&mut self, mipmap: &Arc) -> u64 { + let key = Arc::as_ptr(mipmap) as usize; + + if let Some(&tex_obj) = self.texture_cache.get(&key) { + return tex_obj; + } + + #[cfg(feature = "cuda")] + let tex_obj = self.create_cuda_texture(mipmap); + + #[cfg(not(feature = "cuda"))] + let tex_obj = 0u64; + + self.texture_cache.insert(key, tex_obj); + tex_obj + } + + #[cfg(feature = "cuda")] + fn create_cuda_texture(&self, mipmap: &MIPMap) -> u64 { + // TODO: Implement with cudarc + // 1. Get image data from mipmap.base_image() + // 2. cudaMallocArray + // 3. cudaMemcpy2DToArray + // 4. cudaCreateTextureObject + // 5. Return handle + 0 + } + #[cfg(not(feature = "cuda"))] unsafe fn alloc_unified(&mut self, layout: Layout) -> *mut u8 { - let ptr = std::alloc::alloc(layout); - self.allocations.push((ptr, layout)); + let ptr = unsafe { std::alloc::alloc(layout) }; + self.buffer.push((ptr, layout)); ptr } - pub fn raw_data(&self) -> &[u8] { - &self.buffer - } + // pub fn raw_data(&self) -> &[u8] { + // &self.buffer + // } } impl Drop for Arena { fn drop(&mut self) { - for (ptr, layout) in self.allocations.drain(..) { + for (ptr, layout) in self.buffer.drain(..) { unsafe { #[cfg(feature = "cuda")] { @@ -120,7 +155,7 @@ impl Upload for Shape { impl Upload for Image { type Target = DeviceImage; fn upload(&self, arena: &mut Arena) -> Ptr { - arena.alloc(self.clone()) + arena.alloc(*self.device_image()) } } @@ -138,23 +173,39 @@ impl Upload for SpectrumTexture { SpectrumTexture::Constant(tex) => GPUSpectrumTexture::Constant(tex.clone()), SpectrumTexture::Checkerboard(tex) => GPUSpectrumTexture::Checkerboard(tex.clone()), SpectrumTexture::Dots(tex) => GPUSpectrumTexture::Dots(tex.clone()), - SpectrumTexture::Image(tex) => GPUSpectrumTexture::Image(tex.clone()), + SpectrumTexture::Image(tex) => { + let tex_obj = arena.get_texture_object(&tex.base.mipmap); + let gpu_img = GPUSpectrumImageTexture { + mapping: tex.base.mapping, + tex_obj, + scale: tex.base.scale, + invert: tex.base.invert, + is_single_channel: tex.base.mipmap.is_single_channel(), + color_space: tex.base.mipmap.color_space.clone().unwrap(), + spectrum_type: tex.spectrum_type, + }; + GPUSpectrumTexture::Image(gpu_img) + } SpectrumTexture::Bilerp(tex) => GPUSpectrumTexture::Bilerp(tex.clone()), SpectrumTexture::Scaled(tex) => { - let child_ptr = tex.texture.upload(arena); + let child_ptr = tex.tex.upload(arena); - let gpu_scaled = GPUFloatScaledTexture { + let gpu_scaled = GPUSpectrumScaledTexture { tex: child_ptr, scale: tex.scale.upload(arena), }; GPUSpectrumTexture::Scaled(gpu_scaled) } - SpectrumTexture::Ptex(tex) => GPUSpectrumTexture::Ptex(tex.clone()), + // SpectrumTexture::Ptex(tex) => { + // let gpu_ptex = GPUSpectrumPtexTexture::new( + // tex.base.filename, + // tex.base.encoding, + // tex.base.scale, + // tex.spectrum_type, + // ); + // GPUSpectrumTexture::Ptex(gpu_ptex) + // } SpectrumTexture::Marble(tex) => GPUSpectrumTexture::Marble(tex.clone()), - SpectrumTexture::RGBConstant(tex) => GPUSpectrumTexture::RGBConstant(tex.clone()), - SpectrumTexture::RGBReflectanceConstant(tex) => { - GPUSpectrumTexture::RGBReflectanceConstant(tex.clone()) - } SpectrumTexture::Mix(tex) => { let tex1_ptr = tex.tex1.upload(arena); let tex2_ptr = tex.tex2.upload(arena); @@ -198,7 +249,7 @@ impl Upload for FloatTexture { FloatTexture::Wrinkled(tex) => GPUFloatTexture::Wrinkled(tex.clone()), FloatTexture::Constant(val) => GPUFloatTexture::Constant(*val), FloatTexture::Scaled(tex) => { - let child_ptr = tex.texture.upload(arena); + let child_ptr = tex.tex.upload(arena); let gpu_scaled = GPUFloatScaledTexture { tex: child_ptr, @@ -230,15 +281,13 @@ impl Upload for FloatTexture { }; GPUFloatTexture::DirectionMix(gpu_dmix) } - FloatTexture::Image(tex) => { - let image_ptr = tex.image.upload(arena); + FloatTexture::Image(tex) => { let gpu_image_tex = GPUFloatImageTexture { - mapping: tex.mapping, + mapping: tex.base.mapping, tex_obj: image_ptr.offset as u64, - scale: tex.scale, - invert: tex.invert, - mapping: tex.mapping, + scale: tex.base.scale, + invert: tex.base.invert, }; GPUFloatTexture::Image(gpu_image_tex) } @@ -258,12 +307,16 @@ impl Upload for RGBToSpectrumTable { type Target = RGBToSpectrumTable; fn upload(&self, arena: &mut Arena) -> Ptr { - let z_ptr = arena.alloc_slice(&self.z_nodes); - let c_ptr = arena.alloc_slice(&self.coeffs); + let n_nodes = self.n_nodes as usize; + let z_slice = unsafe { from_raw_parts(self.z_nodes.as_raw(), n_nodes) }; + let coeffs_slice = unsafe { from_raw_parts(self.coeffs.as_raw(), n_nodes) }; + let (z_ptr, _) = arena.alloc_slice(z_slice); + let (c_ptr, _) = arena.alloc_slice(coeffs_slice); let shared_table = RGBToSpectrumTable { z_nodes: z_ptr, coeffs: c_ptr, + n_nodes: self.n_nodes, }; arena.alloc(shared_table) @@ -291,8 +344,8 @@ impl Upload for RGBColorSpace { } } -impl Upload for StandardColorSpaces { - type Target = StandardColorSpaces; +impl Upload for DeviceStandardColorSpaces { + type Target = DeviceStandardColorSpaces; fn upload(&self, arena: &mut Arena) -> Ptr { let srgb_ptr = self.srgb.upload(arena); @@ -300,7 +353,7 @@ impl Upload for StandardColorSpaces { let rec_ptr = self.rec2020.upload(arena); let aces_ptr = self.aces2065_1.upload(arena); - let registry = StandardColorSpaces { + let registry = DeviceStandardColorSpaces { srgb: srgb_ptr, dci_p3: dci_ptr, rec2020: rec_ptr, @@ -315,15 +368,15 @@ impl Upload for PiecewiseConstant2D { type Target = DevicePiecewiseConstant2D; fn upload(&self, arena: &mut Arena) -> Ptr { - let marginal_shared = self.p_marginal.to_shared(arena); + let marginal_shared = self.marginal.to_shared(arena); let conditionals_shared: Vec = self - .p_conditionals + .conditionals .iter() .map(|c| c.to_shared(arena)) .collect(); - let conditionals_ptr = arena.alloc_slice(&conditionals_shared); + let (conditionals_ptr, _) = arena.alloc_slice(&conditionals_shared); let shared_2d = DevicePiecewiseConstant2D { conditionals: conditionals_ptr, @@ -339,44 +392,44 @@ impl Upload for TriangleMesh { type Target = DeviceTriangleMesh; fn upload(&self, arena: &mut Arena) -> Ptr { - let storage = &self._storage; + let storage = &self.storage; // Upload all arrays to arena - let vertex_indices_ptr = arena.alloc_slice(&storage.vertex_indices); - let p_ptr = arena.alloc_slice(&storage.p); + let (vertex_indices_ptr, _) = arena.alloc_slice(&storage.vertex_indices); + let (p_ptr, _) = arena.alloc_slice(&storage.p); - let n_ptr = if storage.n.is_empty() { - std::ptr::null() + let (n_ptr, _) = if storage.n.is_empty() { + (Ptr::null(), 0) } else { - arena.alloc_slice(&storage.n).as_ptr() + arena.alloc_slice(&storage.n) }; - let s_ptr = if storage.s.is_empty() { - std::ptr::null() + let (s_ptr, _) = if storage.s.is_empty() { + (Ptr::null(), 0) } else { - arena.alloc_slice(&storage.s).as_ptr() + arena.alloc_slice(&storage.s) }; - let uv_ptr = if storage.uv.is_empty() { - std::ptr::null() + let (uv_ptr, _) = if storage.uv.is_empty() { + (Ptr::null(), 0) } else { - arena.alloc_slice(&storage.uv).as_ptr() + arena.alloc_slice(&storage.uv) }; - let face_indices_ptr = if storage.face_indices.is_empty() { - std::ptr::null() + let (face_indices_ptr, _) = if storage.face_indices.is_empty() { + (Ptr::null(), 0) } else { - arena.alloc_slice(&storage.face_indices).as_ptr() + arena.alloc_slice(&storage.face_indices) }; let device = DeviceTriangleMesh { - vertex_indices: vertex_indices_ptr.as_ptr(), - p: p_ptr.as_ptr(), + vertex_indices: vertex_indices_ptr, + p: p_ptr, n: n_ptr, s: s_ptr, uv: uv_ptr, face_indices: face_indices_ptr, - ..self.device // Copy n_triangles, n_vertices, flags + ..self.device }; arena.alloc(device) @@ -387,33 +440,28 @@ impl Upload for BilinearPatchMesh { type Target = DeviceBilinearPatchMesh; fn upload(&self, arena: &mut Arena) -> Ptr { - let storage = &self._storage; + let storage = &self.storage; - let vertex_indices_ptr = arena.alloc_slice(&storage.vertex_indices); - let p_ptr = arena.alloc_slice(&storage.p); + let (vertex_indices_ptr, _) = arena.alloc_slice(&storage.vertex_indices); + let (p_ptr, _) = arena.alloc_slice(&storage.p); - let n_ptr = if storage.n.is_empty() { - std::ptr::null() + let (n_ptr, _) = if storage.n.is_empty() { + (Ptr::null(), 0) } else { - arena.alloc_slice(&storage.n).as_ptr() + arena.alloc_slice(&storage.n) }; - let uv_ptr = if storage.uv.is_empty() { - std::ptr::null() + let (uv_ptr, _) = if storage.uv.is_empty() { + (Ptr::null(), 0) } else { - arena.alloc_slice(&storage.uv).as_ptr() + arena.alloc_slice(&storage.uv) }; - let image_dist_ptr = if let Some(ref dist) = storage.image_distribution { - let uploaded = dist.upload(arena); - uploaded.as_ptr() - } else { - std::ptr::null() - }; + let image_dist_ptr = storage.image_distribution.upload(arena); let device = DeviceBilinearPatchMesh { - vertex_indices: vertex_indices_ptr.as_ptr(), - p: p_ptr.as_ptr(), + vertex_indices: vertex_indices_ptr, + p: p_ptr, n: n_ptr, uv: uv_ptr, image_distribution: image_dist_ptr, diff --git a/src/utils/containers.rs b/src/utils/containers.rs index 4966d90..3e3a959 100644 --- a/src/utils/containers.rs +++ b/src/utils/containers.rs @@ -2,6 +2,7 @@ use parking_lot::Mutex; use shared::core::geometry::{Bounds2i, Point2i}; use shared::utils::containers::Array2D; use std::collections::HashSet; +use std::hash::Hash; use std::ops::{Deref, DerefMut}; use std::sync::Arc; @@ -11,7 +12,7 @@ pub struct InternCache { impl InternCache where - T: Eq + Clone, + T: Eq + Clone + Hash, { pub fn new() -> Self { Self { @@ -20,14 +21,14 @@ where } pub fn lookup(&self, value: T) -> Arc { - let mut lock = self.cache.lock().unwrap(); + let mut lock = self.cache.lock(); if let Some(existing) = lock.get(&value) { return existing.clone(); } let new_item = Arc::new(value); - lock.insert(new_item.clone()); + lock.insert(Arc::clone(&new_item)); new_item } } @@ -52,7 +53,7 @@ impl DerefMut for Array2DBuffer { impl Array2DBuffer { fn from_vec(extent: Bounds2i, mut storage: Vec) -> Self { - let width = extent.p_max.x - extent.p_min.x; + let width = extent.p_max.x() - extent.p_min.x(); let view = Array2D { extent, values: storage.as_mut_ptr(), @@ -73,12 +74,12 @@ impl Array2DBuffer { } pub fn new_dims(nx: i32, ny: i32) -> Self { - let extent = Bounds2i::new(Point2i::new(0, 0), Point2i::new(nx, ny)); + let extent = Bounds2i::from_points(Point2i::new(0, 0), Point2i::new(nx, ny)); Self::new(extent) } pub fn new_filled(nx: i32, ny: i32, val: T) -> Self { - let extent = Bounds2i::new(Point2i::new(0, 0), Point2i::new(nx, ny)); + let extent = Bounds2i::from_points(Point2i::new(0, 0), Point2i::new(nx, ny)); let n = (nx * ny) as usize; let storage = vec![val; n]; Self::from_vec(extent, storage) diff --git a/src/utils/math.rs b/src/utils/math.rs index caf9172..5dd318e 100644 --- a/src/utils/math.rs +++ b/src/utils/math.rs @@ -1,39 +1,55 @@ use shared::Float; -use shared::utils::Ptr; use shared::utils::hash::hash_buffer; -use shared::utils::math::{DigitPermutation, PRIMES, permutation_element}; +use shared::utils::math::{DeviceDigitPermutation, PRIMES, permutation_element}; -pub fn new_digit_permutation(base: u32, seed: u64) -> Vec { - let mut n_digits: u32 = 0; - let inv_base = 1. / base as Float; - let mut inv_base_m = 1.; +pub struct DigitPermutation { + pub permutations: Vec, + pub device: DeviceDigitPermutation, +} - while 1.0 - ((base as Float - 1.0) * inv_base_m) < 1.0 { - n_digits += 1; - inv_base_m *= inv_base; - } +impl DigitPermutation { + pub fn new(base: i32, seed: u64) -> Self { + assert!(base < 65536); + let mut n_digits: u32 = 0; + let inv_base = 1. / base as Float; + let mut inv_base_m = 1.; - let mut permutations = vec![0u16; n_digits as usize * base as usize]; + while 1.0 - ((base as Float - 1.0) * inv_base_m) < 1.0 { + n_digits += 1; + inv_base_m *= inv_base; + } - for digit_index in 0..n_digits { - let hash_input = [base as u64, digit_index as u64, seed]; - let dseed = hash_buffer(&hash_input, 0); + let mut permutations = vec![0u16; n_digits as usize * base as usize]; - for digit_value in 0..base { - let index = (digit_index * base + digit_value) as usize; + for digit_index in 0..n_digits { + let hash_input = [base as u64, digit_index as u64, seed]; + let dseed = hash_buffer(&hash_input, 0); - permutations[index] = - permutation_element(digit_value as u32, base as u32, dseed as u32) as u16; + for digit_value in 0..base { + let index = (digit_index as i32 * base + digit_value) as usize; + + permutations[index] = + permutation_element(digit_value as u32, base as u32, dseed as u32) as u16; + } + } + + let device = DeviceDigitPermutation { + base, + n_digits, + permutations: permutations.as_ptr().into(), + }; + + Self { + device, + permutations, } } - - permutations } pub fn compute_radical_inverse_permutations(seed: u64) -> (Vec, Vec) { let temp_data: Vec> = PRIMES .iter() - .map(|&base| new_digit_permutation(base as u32, seed)) + .map(|&base| DigitPermutation::new(base as i32, seed).permutations) .collect(); let mut storage: Vec = Vec::with_capacity(temp_data.iter().map(|v| v.len()).sum()); @@ -42,25 +58,23 @@ pub fn compute_radical_inverse_permutations(seed: u64) -> (Vec, Vec, - color_space: Option, - wrap_mode: WrapMode, - options: MIPMapFilterOptions, + pub pyramid: Vec, + pub color_space: Option, + pub wrap_mode: WrapMode, + pub options: MIPMapFilterOptions, } impl MIPMap { @@ -144,6 +147,11 @@ impl MIPMap { self.pyramid[level].resolution() } + #[inline] + pub fn is_single_channel(&self) -> bool { + self.pyramid[0].n_channels() == 1 + } + pub fn levels(&self) -> usize { self.pyramid.len() } @@ -156,6 +164,10 @@ impl MIPMap { &self.pyramid[level] } + pub fn base_image(&self) -> &Image { + &self.pyramid[0] + } + pub fn filter( &self, st: Point2f, @@ -335,6 +347,83 @@ impl MIPMap { options, )) } + + #[cfg(feature = "cuda")] + pub fn texture_object(&self) -> u64 { + *self + .tex_obj + .get_or_init(|| create_cuda_texture(&self.pyramid, self.wrap_mode)) + } + + #[cfg(not(feature = "cuda"))] + pub fn texture_object(&self) -> u64 { + 0 + } +} + +#[cfg(feature = "cuda")] +fn create_cuda_texture(pyramid: &[Image], wrap_mode: WrapMode) -> u64 { + use cuda_runtime_sys::*; + + let base = &pyramid[0]; + let (width, height) = ( + base.resolution().x() as usize, + base.resolution().y() as usize, + ); + let channels = base.n_channels(); + + unsafe { + let channel_desc = cudaCreateChannelDesc( + 32, + if channels > 1 { 32 } else { 0 }, + if channels > 2 { 32 } else { 0 }, + if channels > 3 { 32 } else { 0 }, + cudaChannelFormatKindFloat, + ); + + let mut array: cudaArray_t = std::ptr::null_mut(); + cudaMallocArray(&mut array, &channel_desc, width, height, 0); + + let pixels = base.as_slice(); // Assuming you have this method + cudaMemcpy2DToArray( + array, + 0, + 0, + pixels.as_ptr() as *const _, + width * channels * std::mem::size_of::(), + width * channels * std::mem::size_of::(), + height, + cudaMemcpyHostToDevice, + ); + + // 4. Create texture object + let res_desc = cudaResourceDesc { + resType: cudaResourceTypeArray, + res: cudaResourceDesc__bindgen_ty_1 { + array: cudaResourceDesc__bindgen_ty_1__bindgen_ty_1 { array }, + }, + }; + + let address_mode = match wrap_mode { + WrapMode::Repeat => cudaAddressModeWrap, + WrapMode::Clamp => cudaAddressModeClamp, + WrapMode::Black => cudaAddressModeBorder, + WrapMode::OctahedralSphere => cudaAddressModeBorder, + }; + + let tex_desc = cudaTextureDesc { + addressMode: [address_mode; 3], + filterMode: cudaFilterModeLinear, + readMode: cudaReadModeElementType, + normalizedCoords: 1, + ..std::mem::zeroed() + }; + + let mut tex_obj: cudaTextureObject_t = 0; + cudaCreateTextureObject(&mut tex_obj, &res_desc, &tex_desc, std::ptr::null()); + + tex_obj + } } static MIP_FILTER_LUT_SIZE: usize = 128; diff --git a/src/utils/parameters.rs b/src/utils/parameters.rs index 4fbc737..53f95a7 100644 --- a/src/utils/parameters.rs +++ b/src/utils/parameters.rs @@ -1,6 +1,7 @@ use crate::core::spectrum::SPECTRUM_CACHE; use crate::core::texture::{FloatTexture, SpectrumTexture}; use crate::spectra::data::get_named_spectrum; +use crate::spectra::piecewise::PiecewiseLinearSpectrumBuffer; use crate::utils::FileLoc; use shared::Float; use shared::core::color::RGB; @@ -132,7 +133,7 @@ impl PBRTParameter for bool { v[0] } fn get_values(param: &ParsedParameter) -> &[Self::Raw] { - param.bools + ¶m.bools } } @@ -144,7 +145,7 @@ impl PBRTParameter for Float { v[0] } fn get_values(param: &ParsedParameter) -> &[Self::Raw] { - param.floats + ¶m.floats } } @@ -157,55 +158,67 @@ impl PBRTParameter for i32 { } fn get_values(param: &ParsedParameter) -> &[Self::Raw] { - param.ints + ¶m.ints } } impl PBRTParameter for Point2f { - type Raw = Point2f; + type Raw = Float; const TYPE_NAME: &'static str = "point2"; const N_PER_ITEM: usize = 2; fn convert(v: &[Self::Raw]) -> Self { Point2f::new(v[0], v[1]) } fn get_values(param: &ParsedParameter) -> &[Self::Raw] { - param.floats + ¶m.floats } } impl PBRTParameter for Point3f { - type Raw = Point3f; + type Raw = Float; const TYPE_NAME: &'static str = "point3"; const N_PER_ITEM: usize = 3; fn convert(v: &[Self::Raw]) -> Self { Point3f::new(v[0], v[1], v[2]) } fn get_values(param: &ParsedParameter) -> &[Self::Raw] { - param.floats + ¶m.floats } } impl PBRTParameter for Vector2f { - type Raw = Vector2f; + type Raw = Float; const TYPE_NAME: &'static str = "vector2"; - const N_PER_ITEM: usize = 3; + const N_PER_ITEM: usize = 2; fn convert(v: &[Self::Raw]) -> Self { Vector2f::new(v[0], v[1]) } fn get_values(param: &ParsedParameter) -> &[Self::Raw] { - param.floats + ¶m.floats } } -impl PBRTParameter for Normal3f { - type Raw = Normal3f; - const TYPE_NAME: &'static str = "normal"; +impl PBRTParameter for Vector3f { + type Raw = Float; + const TYPE_NAME: &'static str = "vector3"; const N_PER_ITEM: usize = 3; fn convert(v: &[Self::Raw]) -> Self { Vector3f::new(v[0], v[1], v[2]) } fn get_values(param: &ParsedParameter) -> &[Self::Raw] { - param.floats + ¶m.floats + } +} + +impl PBRTParameter for Normal3f { + type Raw = Float; + const TYPE_NAME: &'static str = "normal"; + const N_PER_ITEM: usize = 3; + fn convert(v: &[Self::Raw]) -> Self { + Normal3f::new(v[0], v[1], v[2]) + } + fn get_values(param: &ParsedParameter) -> &[Self::Raw] { + ¶m.floats } } @@ -217,7 +230,7 @@ impl PBRTParameter for String { v[0].clone() } fn get_values(param: &ParsedParameter) -> &[Self::Raw] { - param.strings + ¶m.strings } } @@ -272,7 +285,7 @@ impl ParameterDictionary { bool::TYPE_NAME => { if p.bools.is_empty() { error_exit( - &p.loc, + Some(&p.loc), &format!( "\"{}\": non-Boolean values provided for Boolean-valued parameter", p.name @@ -292,7 +305,7 @@ impl ParameterDictionary { | "blackbody" => { if p.ints.is_empty() && p.floats.is_empty() { error_exit( - &p.loc, + Some(&p.loc), &format!( "\"{}\": non-numeric values provided for numeric-valued parameter", p.name @@ -304,7 +317,7 @@ impl ParameterDictionary { String::TYPE_NAME | "texture" => { if p.strings.is_empty() { error_exit( - &p.loc, + Some(&p.loc), &format!( "\"{}\": non-string values provided for string-valued parameter", p.name @@ -316,7 +329,7 @@ impl ParameterDictionary { "spectrum" => { if p.strings.is_empty() && p.ints.is_empty() && p.floats.is_empty() { error_exit( - &p.loc, + Some(&p.loc), &format!( "\"{}\": expecting string or numeric-valued parameter for spectrum parameter", p.name @@ -327,7 +340,7 @@ impl ParameterDictionary { unknown => { error_exit( - &p.loc, + Some(&p.loc), &format!("\"{}\": unknown parameter type '{}'", p.name, unknown), ); } @@ -339,20 +352,20 @@ impl ParameterDictionary { where T: PBRTParameter, { - let param = self.params; + let param = self.params[0].clone(); if param.name == name && param.type_name == T::TYPE_NAME { - let values = T::get_values(param); + let values = T::get_values(¶m); if values.is_empty() { error_exit( - ¶m.loc, + Some(¶m.loc), &format!("No values provided for parameter \"{}\".", name), ); } if values.len() != T::N_PER_ITEM { error_exit( - ¶m.loc, + Some(¶m.loc), &format!( "Expected {} values for parameter \"{}\". Found {}.", T::N_PER_ITEM, @@ -379,7 +392,7 @@ impl ParameterDictionary { if values.len() % T::N_PER_ITEM != 0 { error_exit( - ¶m.loc, + Some(¶m.loc), &format!( "Number of values for \"{}\" is not a multiple of {}", name, @@ -412,7 +425,7 @@ impl ParameterDictionary { } pub fn get_one_string(&self, name: &str, def: &str) -> String { - self.lookup_single(name, def) + self.lookup_single(name, def.to_string()) } pub fn get_one_point2f(&self, name: &str, def: Point2f) -> Point2f { @@ -561,8 +574,8 @@ impl ParameterDictionary { ) -> Vec { match param.type_name.as_str() { "rgb" | "color" => self.extract_rgb_spectrum(param, spectrum_type), - "blackbody" => self.extract_blackbody_spectrum(param), - "spectrum" => self.extract_complex_spectrum(param), + "blackbody" => self.extract_file_spectrum(param), + "spectrum" => self.extract_sampled_spectrum(param), _ => Vec::new(), } } @@ -628,7 +641,7 @@ impl ParameterDictionary { ); } - let (lambdas, values): (Vec, Vec) = param + let (lambdas, values): (Vec, Vec) = param .floats .chunks(2) .enumerate() @@ -642,14 +655,14 @@ impl ParameterDictionary { lam ); } - (lam, val) + (lam as Float, val as Float) }) .unzip(); - vec![Spectrum::PiecewiseLinear(PiecewiseLinearSpectrum { - lambdas, - values, - count: lambdas.len(), + vec![Spectrum::Piecewise(PiecewiseLinearSpectrum { + lambdas: lambdas.as_ptr().into(), + values: values.as_ptr().into(), + count: lambdas.len() as u32, })] } @@ -670,19 +683,19 @@ impl ParameterDictionary { fn read_spectrum_from_file(filename: &str) -> Result { let fn_key = filename.to_string(); { - let cache = SPECTRUM_CACHE.lock().unwrap(); + let cache = SPECTRUM_CACHE.lock(); if let Some(s) = cache.get(&fn_key) { return Ok(s.clone()); } } - let pls = PiecewiseLinearSpectrum::read(&fn_key) + let pls = PiecewiseLinearSpectrumBuffer::read(&fn_key) .ok_or_else(|| format!("unable to read or parse spectrum file '{}'", fn_key))?; - let spectrum = Spectrum::PiecewiseLinear(pls); + let spectrum = Spectrum::Piecewise(*pls); { - let mut cache = SPECTRUM_CACHE.lock().unwrap(); + let mut cache = SPECTRUM_CACHE.lock(); cache.insert(fn_key, spectrum.clone()); } @@ -794,7 +807,7 @@ impl TextureParameterDictionary { if tex.is_some() { tex } else if val.is_some() { - Some(Arc::new(SpectrumTexture::SpectrumConstant( + Some(Arc::new(SpectrumTexture::Constant( SpectrumConstantTexture::new(val.unwrap()), ))) } else { @@ -806,7 +819,7 @@ impl TextureParameterDictionary { if let Some(tex) = self.get_float_texture_or_null(name) { return tex; } else { - return Arc::new(FloatTexture::FloatConstant(FloatConstantTexture::new(val))); + return Arc::new(FloatTexture::Constant(FloatConstantTexture::new(val))); } } @@ -878,14 +891,14 @@ impl TextureParameterDictionary { Spectrum::RGBAlbedo(RGBAlbedoSpectrum::new(cs, rgb)) } }; - return Some(Arc::new(SpectrumTexture::SpectrumConstant( + return Some(Arc::new(SpectrumTexture::Constant( SpectrumConstantTexture::new(s), ))); } "spectrum" | "blackbody" => { let s = self.dict.get_one_spectrum(name, None, stype)?; - return Some(Arc::new(SpectrumTexture::SpectrumConstant( + return Some(Arc::new(SpectrumTexture::Constant( SpectrumConstantTexture::new(s), ))); } @@ -930,9 +943,9 @@ impl TextureParameterDictionary { } "float" => { let v = self.get_one_float(name, 0.); - return Some(Arc::new(FloatTexture::FloatConstant( - FloatConstantTexture::new(v), - ))); + return Some(Arc::new(FloatTexture::Constant(FloatConstantTexture::new( + v, + )))); } _ => { panic!("[{:?}] Couldn't find float texture", p.loc); diff --git a/src/utils/parser.rs b/src/utils/parser.rs index 9a115e9..d5995ae 100644 --- a/src/utils/parser.rs +++ b/src/utils/parser.rs @@ -753,7 +753,9 @@ impl<'a> SceneParser<'a> { 'A' => match token.text.as_str() { "AttributeBegin" => self.target.attribute_begin(token.loc), "AttributeEnd" => self.target.attribute_end(token.loc), - "Attribute" => self.parse_basic_entry(|t, n, p, l| t.attribute(n, p, l))?, + "Attribute" => { + self.parse_basic_entry(|t, n, p, l| t.attribute(n, p.to_vec(), l))? + } "ActiveTransform" => { let a = self.next_token_required()?; match a.text.as_str() { diff --git a/src/utils/sampling.rs b/src/utils/sampling.rs index 9e51e38..0e7c148 100644 --- a/src/utils/sampling.rs +++ b/src/utils/sampling.rs @@ -4,7 +4,8 @@ use shared::Float; use shared::core::geometry::{Point2i, Vector2f, Vector2i}; use shared::utils::Ptr; use shared::utils::sampling::{ - AliasTable, Bin, DevicePiecewiseConstant1D, DevicePiecewiseConstant2D, PiecewiseLinear2D, + AliasTable, Bin, DevicePiecewiseConstant1D, DevicePiecewiseConstant2D, DeviceSummedAreaTable, + DeviceWindowedPiecewiseConstant2D, PiecewiseLinear2D, }; use std::sync::Arc; @@ -114,13 +115,22 @@ impl std::ops::Deref for PiecewiseConstant1D { } } +#[derive(Debug, Clone)] pub struct PiecewiseConstant2D { - conditionals: Vec, - marginal: PiecewiseConstant1D, - conditional_devices: Box<[DevicePiecewiseConstant1D]>, + pub conditionals: Vec, + pub marginal: PiecewiseConstant1D, + pub conditional_devices: Box<[DevicePiecewiseConstant1D]>, pub device: DevicePiecewiseConstant2D, } +impl std::ops::Deref for PiecewiseConstant2D { + type Target = DevicePiecewiseConstant2D; + + fn deref(&self) -> &Self::Target { + &self.device + } +} + impl PiecewiseConstant2D { pub fn new(data: &[Float], n_u: usize, n_v: usize) -> Self { assert_eq!(data.len(), n_u * n_v); @@ -149,7 +159,7 @@ impl PiecewiseConstant2D { .into_boxed_slice(); let device = DevicePiecewiseConstant2D { - conditionals: conditional_devices.as_ptr(), + conditionals: conditional_devices.as_ptr().into(), marginal: marginal.device, n_u: n_u as u32, n_v: n_v as u32, @@ -331,7 +341,7 @@ impl PiecewiseLinear2DHost { } } -#[derive(Copy, Debug, Clone)] +#[derive(Debug, Clone)] pub struct AliasTableHost { pub view: AliasTable, _storage: Vec, @@ -421,3 +431,71 @@ impl AliasTableHost { } } } + +#[derive(Clone, Debug)] +pub struct SummedAreaTable { + pub device: DeviceSummedAreaTable, + sum: Array2D, +} + +impl std::ops::Deref for SummedAreaTable { + type Target = DeviceSummedAreaTable; + fn deref(&self) -> &Self::Target { + &self.device + } +} + +impl SummedAreaTable { + pub fn new(values: &Array2D) -> Self { + let width = values.x_size(); + let height = values.y_size(); + + let mut sum = Array2D::::new_with_dims(width, height); + sum[(0, 0)] = values[(0, 0)] as f64; + + for x in 1..width { + sum[(x, 0)] = values[(x as i32, 0)] as f64 + sum[(x - 1, 0)]; + } + + for y in 1..height { + sum[(0, y)] = values[(0, y as i32)] as f64 + sum[(0, y - 1)]; + } + + for y in 1..height { + for x in 1..width { + let term = values[(x as i32, y as i32)] as f64; + let left = sum[(x - 1, y)]; + let up = sum[(x, y - 1)]; + let diag = sum[(x - 1, y - 1)]; + + sum[(x, y)] = term + left + up - diag; + } + } + + let device = DeviceSummedAreaTable { sum }; + Self { device, sum } + } +} + +#[derive(Clone, Debug)] +pub struct WindowedPiecewiseConstant2D { + pub device: DeviceWindowedPiecewiseConstant2D, + sat: DeviceSummedAreaTable, + func: Array2D, +} + +impl std::ops::Deref for WindowedPiecewiseConstant2D { + type Target = DeviceWindowedPiecewiseConstant2D; + fn deref(&self) -> &Self::Target { + &self.device + } +} + +impl WindowedPiecewiseConstant2D { + pub fn new(func: Array2D) -> Self { + let sat = *SummedAreaTable::new(&func); + let device = DeviceWindowedPiecewiseConstant2D { sat, func }; + + Self { sat, func, device } + } +}