diff --git a/shared/src/bxdfs/complex.rs b/shared/src/bxdfs/complex.rs index aaf6eef..f091941 100644 --- a/shared/src/bxdfs/complex.rs +++ b/shared/src/bxdfs/complex.rs @@ -428,9 +428,7 @@ impl BxDFTrait for NormalizedFresnelBxDF { BxDFFlags::REFLECTION | BxDFFlags::DIFFUSE } - fn regularize(&mut self) { - return; - } + fn regularize(&mut self) {} fn as_any(&self) -> &dyn Any { self @@ -462,9 +460,7 @@ impl BxDFTrait for EmptyBxDF { BxDFFlags::UNSET } - fn regularize(&mut self) { - return; - } + fn regularize(&mut self) {} fn as_any(&self) -> &dyn Any { self diff --git a/shared/src/core/camera.rs b/shared/src/core/camera.rs index e1328ed..66acae6 100644 --- a/shared/src/core/camera.rs +++ b/shared/src/core/camera.rs @@ -117,9 +117,9 @@ pub struct CameraBase { pub medium: Ptr, } -#[enum_dispatch(CameraTrait)] #[repr(C)] #[derive(Debug, Copy, Clone)] +#[enum_dispatch(CameraTrait)] pub enum Camera { Perspective(PerspectiveCamera), Orthographic(OrthographicCamera), @@ -141,7 +141,7 @@ pub trait CameraTrait { ); } } - &*self.base().film + &self.base().film } fn sample_time(&self, u: Float) -> Float { diff --git a/shared/src/core/film.rs b/shared/src/core/film.rs index e13e52b..b4dd6da 100644 --- a/shared/src/core/film.rs +++ b/shared/src/core/film.rs @@ -116,9 +116,9 @@ impl RGBFilm { let pixel = &self.pixels[p_film]; for c in 0..3 { - pixel.rgb_sum[c].add((weight * rgb[c as u32]) as f32); + pixel.rgb_sum[c].add(weight * rgb[c as u32]); } - pixel.weight_sum.add(weight as f32); + pixel.weight_sum.add(weight); } pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) { @@ -353,6 +353,9 @@ pub struct SpectralFilm { pub bucket_splats: *mut AtomicFloat, } +unsafe impl Send for SpectralFilm {} +unsafe impl Sync for SpectralFilm {} + impl SpectralFilm { pub fn base(&self) -> &FilmBase { &self.base @@ -474,7 +477,7 @@ pub struct FilmBase { pub pixel_bounds: Bounds2i, pub filter: Filter, pub diagonal: Float, - pub sensor: *const PixelSensor, + pub sensor: Ptr, } #[repr(C)] @@ -486,6 +489,9 @@ pub enum Film { Spectral(SpectralFilm), } +unsafe impl Send for Film {} +unsafe impl Sync for Film {} + impl Film { pub fn base(&self) -> &FilmBase { match self { diff --git a/shared/src/core/image.rs b/shared/src/core/image.rs index 7d3da0a..ff7e406 100644 --- a/shared/src/core/image.rs +++ b/shared/src/core/image.rs @@ -179,7 +179,7 @@ impl ImageAccess for DeviceImage { self.base().encoding.to_linear_scalar(raw_u8) } Pixels::F16(ptr) => { - let half_bits: u16 = *ptr.add(offset as usize) as u16; + let half_bits: u16 = *ptr.add(offset as usize); f16_to_f32(half_bits) } Pixels::F32(ptr) => *ptr.add(offset as usize), diff --git a/shared/src/core/primitive.rs b/shared/src/core/primitive.rs index 33f1b4d..c11d540 100644 --- a/shared/src/core/primitive.rs +++ b/shared/src/core/primitive.rs @@ -23,11 +23,11 @@ pub trait PrimitiveTrait { #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct GeometricPrimitive { - shape: Ptr, - material: Ptr, - area_light: Ptr, - medium_interface: MediumInterface, - alpha: Ptr, + pub shape: Ptr, + pub material: Ptr, + pub area_light: Ptr, + pub medium_interface: MediumInterface, + pub alpha: Ptr, } unsafe impl Send for GeometricPrimitive {} @@ -91,8 +91,22 @@ impl PrimitiveTrait for GeometricPrimitive { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct SimplePrimitive { - shape: Ptr, - material: Ptr, + pub shape: Ptr, + pub material: Ptr, +} + +impl PrimitiveTrait for SimplePrimitive { + fn bounds(&self) -> Bounds3f { + todo!() + } + + fn intersect(&self, r: &Ray, t_max: Option) -> Option { + todo!() + } + + fn intersect_p(&self, r: &Ray, t_max: Option) -> bool { + todo!() + } } #[derive(Debug, Clone)] @@ -221,6 +235,7 @@ impl PrimitiveTrait for KdTreeAggregate { #[derive(Clone, Debug)] #[enum_dispatch(PrimitiveTrait)] pub enum Primitive { + Simple(SimplePrimitive), Geometric(GeometricPrimitive), Transformed(TransformedPrimitive), Animated(AnimatedPrimitive), diff --git a/shared/src/core/texture.rs b/shared/src/core/texture.rs index 6b4450d..048b761 100644 --- a/shared/src/core/texture.rs +++ b/shared/src/core/texture.rs @@ -350,7 +350,6 @@ pub enum GPUFloatTexture { FBm(FBmTexture), Windy(WindyTexture), Wrinkled(WrinkledTexture), - Ptex(GPUFloatPtexTexture), Image(GPUFloatImageTexture), Mix(GPUFloatMixTexture), } @@ -367,7 +366,6 @@ impl GPUFloatTexture { GPUFloatTexture::FBm(t) => t.evaluate(ctx), GPUFloatTexture::Windy(t) => t.evaluate(ctx), GPUFloatTexture::Wrinkled(t) => t.evaluate(ctx), - GPUFloatTexture::Ptex(t) => t.evaluate(ctx), GPUFloatTexture::Image(t) => t.evaluate(ctx), GPUFloatTexture::Mix(t) => t.evaluate(ctx), } diff --git a/src/core/film.rs b/src/core/film.rs index 30b53ef..8103116 100644 --- a/src/core/film.rs +++ b/src/core/film.rs @@ -392,7 +392,11 @@ impl FilmBaseHost for FilmBase { } } -pub trait FilmHost { +#[enum_dispatch] +pub trait FilmTrait: Sync { + fn base(&self) -> &FilmBase; + fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option) -> RGB; + fn get_filename(&self) -> &str; fn write_image(&self, metadata: &ImageMetadata, splat_scale: Float) { let image = self.get_image(metadata, splat_scale); image @@ -472,7 +476,32 @@ pub trait FilmHost { image } - fn get_filename(&self) -> &str; +} + +impl FilmTrait for Film { + fn base(&self) -> &FilmBase { + match self { + Film::RGB(f) => &f.base, + Film::GBuffer(f) => &f.base, + Film::Spectral(f) => &f.base, + } + } + + fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option) -> RGB { + match self { + Film::RGB(f) => f.get_pixel_rgb(p, splat_scale), + Film::GBuffer(f) => f.get_pixel_rgb(p, splat_scale), + Film::Spectral(f) => f.get_pixel_rgb(p, splat_scale), + } + } + + fn get_filename(&self) -> &str { + match self { + Film::RGB(f) => &f.filename, + Film::GBuffer(f) => &f.filename, + Film::Spectral(f) => &f.filename, + } + } } pub trait FilmFactory { diff --git a/src/core/image/mod.rs b/src/core/image/mod.rs index c32a8ca..871bfa4 100644 --- a/src/core/image/mod.rs +++ b/src/core/image/mod.rs @@ -1,3 +1,4 @@ +use crate::utils::containers::Array2D; use anyhow::Result; use half::f16; use shared::Float; @@ -5,7 +6,6 @@ use shared::core::color::ColorEncoding; use shared::core::color::LINEAR; use shared::core::geometry::{Bounds2f, Point2f, Point2i}; use shared::core::image::{DeviceImage, ImageBase, PixelFormat, Pixels, WrapMode, WrapMode2D}; -use shared::utils::containers::DeviceArray2D; use shared::utils::math::square; use smallvec::{SmallVec, smallvec}; use std::ops::{Deref, DerefMut}; @@ -470,14 +470,14 @@ impl Image { Self::from_storage(new_storage, res, new_names, self.encoding()) } - pub fn get_sampling_distribution(&self, dxd_a: F, domain: Bounds2f) -> DeviceArray2D + pub fn get_sampling_distribution(&self, dxd_a: F, domain: Bounds2f) -> Array2D where F: Fn(Point2f) -> Float + Sync + Send, { let width = self.resolution().x(); let height = self.resolution().y(); - let mut dist = DeviceArray2D::new_with_dims(width as usize, height as usize); + let mut dist = Array2D::new_dims(width, height); dist.values .par_chunks_mut(width as usize) @@ -500,7 +500,7 @@ impl Image { dist } - pub fn get_sampling_distribution_uniform(&self) -> DeviceArray2D { + pub fn get_sampling_distribution_uniform(&self) -> Array2D { let default_domain = Bounds2f::from_points(Point2f::new(0.0, 0.0), Point2f::new(1.0, 1.0)); self.get_sampling_distribution(|_| 1.0, default_domain) diff --git a/src/core/light.rs b/src/core/light.rs index 8e1b059..9073b55 100644 --- a/src/core/light.rs +++ b/src/core/light.rs @@ -1,20 +1,21 @@ -use shared::core::light::Light; use crate::core::spectrum::SPECTRUM_CACHE; use crate::core::texture::FloatTexture; +use crate::spectra::DenselySampledSpectrumBuffer; use crate::utils::containers::InternCache; use crate::utils::{Arena, FileLoc, ParameterDictionary}; use anyhow::{Result, anyhow}; use shared::core::camera::CameraTransform; +use shared::core::light::Light; use shared::core::medium::Medium; use shared::core::shape::Shape; use shared::core::spectrum::Spectrum; use shared::lights::*; -use shared::spectra::{DenselySampledSpectrum, RGBColorSpace}; +use shared::spectra::RGBColorSpace; use shared::utils::Transform; -pub fn lookup_spectrum(s: &Spectrum) -> DenselySampledSpectrum { +pub fn lookup_spectrum(s: &Spectrum) -> DenselySampledSpectrumBuffer { let cache = SPECTRUM_CACHE.get_or_init(InternCache::new); - let dense_spectrum = DenselySampledSpectrum::from_spectrum(s); + let dense_spectrum = DenselySampledSpectrumBuffer::from_spectrum(s); cache.lookup(dense_spectrum).as_ref() } diff --git a/src/core/material.rs b/src/core/material.rs index ea937c2..26005c4 100644 --- a/src/core/material.rs +++ b/src/core/material.rs @@ -40,42 +40,46 @@ impl MaterialFactory for Material { ) -> Result { match name { "diffuse" => { - DiffuseMaterial::create(parameters, normal_map, named_materials, loc, arena)? + DiffuseMaterial::create(parameters, normal_map, &named_materials, &loc, arena) } "coateddiffuse" => { - CoatedDiffuseMaterial::create(parameters, normal_map, named_materials, loc, arena)? + CoatedDiffuseMaterial::create(parameters, normal_map, &named_materials, &loc, arena) } "coatedconductor" => CoatedConductorMaterial::create( parameters, normal_map, - named_materials, - loc, + &named_materials, + &loc, arena, - )?, + ), "diffusetransmission" => DiffuseTransmissionMaterial::create( parameters, normal_map, - named_materials, - loc, + &named_materials, + &loc, arena, - )?, + ), "dielectric" => { - DielectricMaterial::create(parameters, normal_map, named_materials, loc, arena)? + DielectricMaterial::create(parameters, normal_map, &named_materials, &loc, arena) } - "thindielectric" => { - ThinDielectricMaterial::create(parameters, normal_map, named_materials, loc, arena)? - } - "hair" => HairMaterial::create(parameters, normal_map, named_materials, loc, arena)?, + "thindielectric" => ThinDielectricMaterial::create( + parameters, + normal_map, + &named_materials, + &loc, + arena, + ), + "hair" => HairMaterial::create(parameters, normal_map, &named_materials, &loc, arena), "conductor" => { - ConductorMaterial::create(parameters, normal_map, named_materials, loc, arena)? + ConductorMaterial::create(parameters, normal_map, &named_materials, &loc, arena) } "measured" => { - MeasuredMaterial::create(parameters, normal_map, named_materials, loc, arena)? + MeasuredMaterial::create(parameters, normal_map, &named_materials, &loc, arena) } "subsurface" => { - SubsurfaceMaterial::create(parameters, normal_map, named_materials, loc, arena)? + SubsurfaceMaterial::create(parameters, normal_map, &named_materials, &loc, arena) } - "mix" => MixMaterial::create(parameters, normal_map, named_materials, loc, arena)?, + "mix" => MixMaterial::create(parameters, normal_map, &named_materials, &loc, arena), _ => Err(anyhow!("Material type '{}' unknown at {}", $name, $loc)), } diff --git a/src/core/medium.rs b/src/core/medium.rs index 94f7f15..21a3895 100644 --- a/src/core/medium.rs +++ b/src/core/medium.rs @@ -1,7 +1,7 @@ use crate::spectra::dense::DenselySampledSpectrumBuffer; use shared::core::geometry::{Bounds3f, Point3i}; use shared::core::medium::{GridMedium, HGPhaseFunction, HomogeneousMedium, RGBGridMedium}; -use shared::core::spectrum::Spectrum; +use shared::core::spectrum::{Spectrum, SpectrumTrait}; use shared::spectra::{DenselySampledSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum}; use shared::utils::Transform; use shared::utils::containers::SampledGrid; diff --git a/src/core/mod.rs b/src/core/mod.rs index b64cb31..f39c4c8 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -8,6 +8,7 @@ pub mod image; pub mod light; pub mod material; pub mod medium; +pub mod primitive; pub mod sampler; pub mod sampler; pub mod scene; diff --git a/src/core/primitive.rs b/src/core/primitive.rs new file mode 100644 index 0000000..da8407a --- /dev/null +++ b/src/core/primitive.rs @@ -0,0 +1,40 @@ +use shared::core::{ + light::Light, + material::{self, Material}, + medium::MediumInterface, + primitive::{GeometricPrimitive, SimplePrimitive}, + shape::Shape, +}; + +use shared::utils::Ptr; + +use crate::core::texture::FloatTexture; + +pub trait CreateSimplePrimitive { + fn new(shape: Ptr, material: Ptr) -> SimplePrimitive { + SimplePrimitive { shape, material } + } +} + +impl CreateSimplePrimitive for SimplePrimitive {} + +pub trait CreateGeometricPrimitive { + fn new( + shape: Ptr, + material: Ptr, + area_light: Ptr, + medium_interface: MediumInterface, + alpha: Ptr, + ) -> GeometricPrimitive { + GeometricPrimitive { + shape, + material, + area_light, + medium_interface, + alpha, + } + } +} + +impl CreateGeometricPrimitive for GeometricPrimitive {} + diff --git a/src/core/sampler.rs b/src/core/sampler.rs index a1ee383..74d43e9 100644 --- a/src/core/sampler.rs +++ b/src/core/sampler.rs @@ -3,7 +3,7 @@ use crate::utils::{FileLoc, ParameterDictionary}; use anyhow::{Result, anyhow}; use shared::core::geometry::Point2i; use shared::core::sampler::{ - HaltonSampler, IndependentSampler, PaddedSobolSampler, Sampler, SobolSampler, + HaltonSampler, IndependentSampler, PaddedSobolSampler, Sampler, SamplerTrait, SobolSampler, StratifiedSampler, ZSobolSampler, }; diff --git a/src/core/scene/builder.rs b/src/core/scene/builder.rs index 443c356..1dbcb2b 100644 --- a/src/core/scene/builder.rs +++ b/src/core/scene/builder.rs @@ -553,7 +553,7 @@ impl ParserTarget for BasicSceneBuilder { &mut self.spectrum_texture_names }; - if names.contains(name) { + if names.contains(&name) { self.error_exit_deferred(&loc, &format!("Redefining texture \"{}\".", name)); return; } @@ -561,7 +561,7 @@ impl ParserTarget for BasicSceneBuilder { } let base = SceneEntity { - name: tex_name, + name: tex_name.to_string(), parameters: dict, loc, }; @@ -578,9 +578,9 @@ impl ParserTarget for BasicSceneBuilder { } fn material(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { - self.verify_world("material", loc); + self.verify_world("material", &loc); let entity = SceneEntity { - name, + name: name.to_string(), loc, parameters: params, }; diff --git a/src/core/scene/scene.rs b/src/core/scene/scene.rs index 67db37b..8eddb53 100644 --- a/src/core/scene/scene.rs +++ b/src/core/scene/scene.rs @@ -1,9 +1,14 @@ use super::entities::*; use super::state::*; +use crate::core::camera::CameraFactory; use crate::core::filter::FilterFactory; use crate::core::image::{Image, io::ImageIO}; use crate::core::material::MaterialFactory; +use crate::core::primitive::{CreateGeometricPrimitive, CreateSimplePrimitive}; +use crate::core::sampler::SamplerFactory; +use crate::core::shape::ShapeFactory; use crate::core::texture::{FloatTexture, SpectrumTexture}; +use crate::utils::arena; use crate::utils::arena::Arena; use crate::utils::error::FileLoc; use crate::utils::parallel::run_async; @@ -13,6 +18,7 @@ use parking_lot::Mutex; use rayon::prelude::*; use shared::core::camera::Camera; use shared::core::color::ColorEncoding; +use shared::core::color::LINEAR; use shared::core::film::Film; use shared::core::filter::Filter; use shared::core::light::Light; @@ -142,7 +148,7 @@ impl BasicScene { 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) + Sampler::create(&sampler.name, &sampler.parameters, res, &sampler.loc, arena) .expect("Sampler was not correctly created") }); self.sampler_state.lock().job = Some(sampler_job); @@ -154,10 +160,11 @@ impl BasicScene { Camera::create( &camera.base.name, &camera.base.parameters, - medium, &camera.camera_transform, + medium, camera_film, &camera.base.loc, + arena, ) .expect("Failed to create camera") }); @@ -221,15 +228,19 @@ impl BasicScene { 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( + let render_from_texture = texture_clone.render_from_object.start_transform; + let tex_dict = + TextureParameterDictionary::new(texture_clone.base.parameters.into(), None); + let texture = FloatTexture::create( &texture_clone.base.name, &render_from_texture, - &tex_dict, - &texture_clone.base.loc, - )) + tex_dict, + texture_clone.base.loc, + arena, + ) + .expect("Could not create Float texture"); + + Arc::new(texture) }); state.float_texture_jobs.insert(name, job); @@ -275,16 +286,20 @@ impl BasicScene { 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( + let render_from_texture = texture_clone.render_from_object.start_transform; + let tex_dict = + TextureParameterDictionary::new(texture_clone.base.parameters.into(), None); + let texture = SpectrumTexture::create( &texture_clone.base.name, &render_from_texture, - &tex_dict, + tex_dict, SpectrumType::Albedo, - &texture_clone.base.loc, - )) + texture_clone.base.loc, + arena, + ) + .expect("Could not crate spectrum texture."); + + Arc::new(texture) }); state.spectrum_texture_jobs.insert(name, job); @@ -337,37 +352,47 @@ impl BasicScene { // Create serial textures (need access to already-loaded textures) let named = NamedTextures { float_textures: float_textures.clone(), - spectrum_textures: spectrum_textures.clone(), + albedo_spectrum_textures: spectrum_textures.clone(), + illuminant_spectrum_textures: spectrum_textures.clone(), + unbounded_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 render_from_texture = entity.render_from_object.start_transform; + let tex_dict = + TextureParameterDictionary::new(entity.base.parameters.into(), Some(named)); let tex = FloatTexture::create( &entity.base.name, - &render_from_texture, - &tex_dict, - &entity.base.loc, - ); + render_from_texture, + tex_dict, + entity.base.loc, + arena, + ) + .expect("Could not create float texture"); 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 render_from_texture = entity.render_from_object.start_transform; + let tex_dict = + TextureParameterDictionary::new(entity.base.parameters.into(), Some(named)); let tex = SpectrumTexture::create( &entity.base.name, - &render_from_texture, - &tex_dict, + render_from_texture, + tex_dict, SpectrumType::Albedo, - &entity.base.loc, - ); + entity.base.loc, + arena, + ) + .expect("Could not create spectrum texture"); spectrum_textures.insert(name, Arc::new(tex)); } NamedTextures { float_textures, - spectrum_textures, + albedo_spectrum_textures: spectrum_textures, + unbounded_spectrum_textures: spectrum_textures, + illuminant_spectrum_textures: spectrum_textures, } } @@ -410,15 +435,18 @@ impl BasicScene { } let normal_map = self.get_normal_map(&state, &entity.parameters); - let tex_dict = TextureParameterDictionary::new(&entity.parameters, Some(textures)); + let tex_dict = + TextureParameterDictionary::new(entity.parameters.into(), Some(*textures)); let mat = Material::create( &mat_type, &tex_dict, normal_map, - &named_materials, - &entity.loc, - ); + named_materials, + entity.loc, + arena, + ) + .expect("Could not create material"); named_materials.insert(name.clone(), mat); } @@ -427,25 +455,24 @@ impl BasicScene { 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 tex_dict = + TextureParameterDictionary::new(entity.parameters.into(), Some(*textures)); let mat = Material::create( &entity.name, &tex_dict, normal_map, - &named_materials, - &entity.loc, - ); + named_materials, + entity.loc, + arena, + ) + .expect("Could not create material"); materials.push(mat); } (named_materials, materials) } - // ======================================================================== - // Finalization: Aggregate - // ======================================================================== - pub fn create_aggregate( &self, arena: &mut Arena, @@ -494,12 +521,13 @@ impl BasicScene { .map(|sh| { Shape::create( &sh.base.name, - sh.render_from_object.as_ref(), - sh.object_from_render.as_ref(), + *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, + sh.base.parameters, + lookup.textures.float_textures, + sh.base.loc, + arena, ) }) .collect() @@ -515,12 +543,13 @@ impl BasicScene { .map(|sh| { Shape::create( &sh.transformed_base.base.name, - sh.identity.as_ref(), - sh.identity.as_ref(), + *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, + sh.transformed_base.base.parameters, + lookup.textures.float_textures, + sh.transformed_base.base.loc, + arena, ) }) .collect() @@ -546,11 +575,19 @@ impl BasicScene { &lookup.textures.float_textures, ); - let mtl = lookup.resolve_material(&entity.material, &entity.base.loc); + let mtl = lookup + .resolve_material(&entity.material, &entity.base.loc) + .unwrap(); let mi = MediumInterface::new( - lookup.find_medium(&entity.inside_medium, &entity.base.loc), - lookup.find_medium(&entity.outside_medium, &entity.base.loc), + lookup + .find_medium(&entity.inside_medium, &entity.base.loc) + .unwrap() + .as_ref(), + lookup + .find_medium(&entity.outside_medium, &entity.base.loc) + .unwrap() + .as_ref(), ); let shape_lights_opt = lookup.shape_lights.get(&i); @@ -569,17 +606,17 @@ impl BasicScene { 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)) + let p = SimplePrimitive::new(shape_ptr, Ptr::from(&mtl)); + Primitive::Simple(p) } else { let p = GeometricPrimitive::new( shape_ptr, - mtl, + Ptr::from(&mtl), area_light, mi.clone(), alpha_tex.clone(), ); - Primitive::Geometric(arena.alloc(p)) + Primitive::Geometric(p) }; primitives.push(prim); @@ -617,7 +654,7 @@ impl BasicScene { } // ======================================================================== - // Private helpers + // Helpers // ======================================================================== fn get_singleton( @@ -655,18 +692,18 @@ impl BasicScene { 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 immeta = Image::read(path, Some(LINEAR)).expect(&format!( + "{}: normal map must contain R, G, B channels", + filename_clone + )); 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 - ) - }); + .expect(&format!( + "{}: normal map must contain R, G, B channels", + filename_clone + )); Arc::new(immeta.image.select_channels(&rgb_desc)) }); @@ -692,21 +729,21 @@ impl BasicScene { 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 - } + let 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> { diff --git a/src/core/shape.rs b/src/core/shape.rs index 69919bf..6224289 100644 --- a/src/core/shape.rs +++ b/src/core/shape.rs @@ -5,6 +5,7 @@ use shared::core::options::get_options; use shared::core::shape::*; use shared::shapes::*; // use shared::spectra::*; +use anyhow::Result; use parking_lot::Mutex; use shared::utils::Transform; use std::collections::HashMap; @@ -19,10 +20,10 @@ pub trait CreateShape { object_from_render: Transform, reverse_orientation: bool, parameters: ParameterDictionary, - float_textures: HashMap, + float_textures: HashMap>, loc: FileLoc, arena: &mut Arena, - ) -> Result, String>; + ) -> Result>; } pub trait ShapeFactory { @@ -32,10 +33,10 @@ pub trait ShapeFactory { object_from_render: Transform, reverse_orientation: bool, parameters: ParameterDictionary, - float_textures: HashMap, + float_textures: HashMap>, loc: FileLoc, arena: &mut Arena, - ) -> Result, String>; + ) -> Result>; } impl ShapeFactory for Shape { @@ -45,10 +46,10 @@ impl ShapeFactory for Shape { object_from_render: Transform, reverse_orientation: bool, parameters: ParameterDictionary, - float_textures: HashMap, + float_textures: HashMap>, loc: FileLoc, arena: &mut Arena, - ) -> Result, String> { + ) -> Result> { match name { "sphere" => SphereShape::create( render_from_object, @@ -58,7 +59,7 @@ impl ShapeFactory for Shape { float_textures, loc, arena, - )?, + ), "cylinder" => CylinderShape::create( render_from_object, object_from_render, @@ -67,7 +68,7 @@ impl ShapeFactory for Shape { float_textures, loc, arena, - )?, + ), "disk" => DiskShape::create( render_from_object, object_from_render, @@ -76,7 +77,7 @@ impl ShapeFactory for Shape { float_textures, loc, arena, - )?, + ), "bilinearmesh" => BilinearPatchShape::create( render_from_object, object_from_render, @@ -85,7 +86,7 @@ impl ShapeFactory for Shape { float_textures, loc, arena, - )?, + ), "trianglemesh" => TriangleShape::create( render_from_object, object_from_render, @@ -94,14 +95,24 @@ impl ShapeFactory for Shape { float_textures, loc, arena, - )?, + ), "plymesh" => { - let filename = resolve_filename(parameters.get_one_string("filename", "")); - let ply_mesh = TriQuadMesh::read_ply(filename); - let mut edge_length = parameters.get_one_float("edgelength", 1.); - edge_length *= get_options().displacement_edge_scale; - let displacement_tex_name = parameters.get_texture("displacement"); + // let filename = resolve_filename(¶meters.get_one_string("filename", "")); + // let ply_mesh = TriQuadMesh::read_ply(filename); + // let mut edge_length = parameters.get_one_float("edgelength", 1.); + // edge_length *= get_options().displacement_edge_scale; + // let displacement_tex_name = parameters.get_texture("displacement"); + TriangleShape::create( + render_from_object, + object_from_render, + reverse_orientation, + parameters, + float_textures, + loc, + arena, + ) } + _ => Err(anyhow!("Unknown shape name")), } } } diff --git a/src/core/spectrum.rs b/src/core/spectrum.rs index f7ccec7..70cb74e 100644 --- a/src/core/spectrum.rs +++ b/src/core/spectrum.rs @@ -7,17 +7,20 @@ use shared::spectra::DenselySampledSpectrum; use std::collections::HashMap; use std::sync::LazyLock; -pub static SPECTRUM_CACHE: LazyLock>> = +pub static SPECTRUM_CACHE: LazyLock> = + LazyLock::new(InternCache::new); + +pub static SPECTRUM_FILE_CACHE: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); -fn get_spectrum_cache() -> &'static InternCache { - SPECTRUM_CACHE.get_or_init(InternCache::new) +pub fn get_spectrum_cache() -> &'static InternCache { + &SPECTRUM_CACHE } pub fn spectrum_to_photometric(s: Spectrum) -> Float { let effective_spectrum = match s { - Spectrum::RGBIlluminant(ill) => &Spectrum::Dense(ill.illuminant), - _ => s, + Spectrum::RGBIlluminant(ill) => &Spectrum::Dense(*ill.illuminant), + _ => &s, }; - effective_spectrum.inner_product(cie_y) + effective_spectrum.inner_product(&cie_y()) } diff --git a/src/core/texture.rs b/src/core/texture.rs index f262cd6..8a00048 100644 --- a/src/core/texture.rs +++ b/src/core/texture.rs @@ -2,11 +2,13 @@ use crate::textures::*; use crate::utils::mipmap::MIPMap; use crate::utils::mipmap::MIPMapFilterOptions; use crate::utils::{Arena, FileLoc, TextureParameterDictionary}; +use anyhow::{Result, anyhow}; use enum_dispatch::enum_dispatch; use shared::Float; use shared::core::color::ColorEncoding; use shared::core::geometry::Vector3f; use shared::core::image::WrapMode; +use shared::core::texture::SpectrumType; use shared::core::texture::{ CylindricalMapping, PlanarMapping, SphericalMapping, TextureEvalContext, TextureMapping2D, UVMapping, @@ -17,10 +19,12 @@ use shared::utils::Transform; use std::collections::HashMap; use std::sync::{Arc, Mutex, OnceLock}; +#[enum_dispatch] pub trait FloatTextureTrait { fn evaluate(&self, ctx: &TextureEvalContext) -> Float; } +#[enum_dispatch] pub trait SpectrumTextureTrait { fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum; } @@ -37,11 +41,16 @@ pub enum FloatTexture { Checkerboard(FloatCheckerboardTexture), Dots(FloatDotsTexture), FBm(FBmTexture), - // Ptex(FloatPtexTexture), Windy(WindyTexture), Wrinkled(WrinkledTexture), } +impl FloatTextureTrait for FloatDotsTexture { + fn evaluate(&self, _ctx: &shared::core::texture::TextureEvalContext) -> shared::Float { + todo!() + } +} + impl FloatTextureTrait for Arc { fn evaluate(&self, ctx: &TextureEvalContext) -> Float { self.as_ref().evaluate(ctx) @@ -51,63 +60,26 @@ impl FloatTextureTrait for Arc { impl FloatTexture { pub fn create( name: &str, - render_from_texture: &Transform, - params: &TextureParameterDictionary, - loc: &FileLoc, + render_from_texture: Transform, + params: TextureParameterDictionary, + loc: FileLoc, arena: &mut Arena, - ) -> Result { + ) -> Result { match name { - "constant" => { - let tex = FloatConstantTexture::create(render_from_texture, params, loc); - Ok(FloatTexture::Constant(tex)) - } - "scale" => Ok(FloatScaledTexture::create( - render_from_texture, - params, - loc, - arena, - )), - "mix" => { - let tex = FloatMixTexture::create(render_from_texture, params, loc, arena); - Ok(FloatTexture::Mix(tex)) - } + "constant" => FloatConstantTexture::create(render_from_texture, params, loc), + "scale" => FloatScaledTexture::create(&render_from_texture, ¶ms, &loc, arena), + "mix" => FloatMixTexture::create(&render_from_texture, ¶ms, &loc, arena), "directionmix" => { - let tex = FloatDirectionMixTexture::create(render_from_texture, params, loc, arena); - Ok(FloatTexture::DirectionMix(tex)) + FloatDirectionMixTexture::create(&render_from_texture, ¶ms, &loc, arena) } - "bilerp" => { - let tex = FloatBilerpTexture::create(render_from_texture, params, loc); - Ok(FloatTexture::Bilerp(tex)) - } - "imagemap" => { - let tex = FloatImageTexture::create(render_from_texture, params, loc); - Ok(FloatTexture::Image(tex)) - } - "checkerboard" => { - let tex = FloatCheckerboardTexture::create(render_from_texture, params, loc); - Ok(FloatTexture::Checkerboard(tex)) - } - "dots" => { - let tex = FloatDotsTexture::create(render_from_texture, params, loc); - Ok(FloatTexture::Dots(tex)) - } - "fbm" => { - let tex = FBmTexture::create(render_from_texture, params, loc); - Ok(FloatTexture::FBm(tex)) - } - "wrinkled" => { - let tex = WrinkledTexture::create(render_from_texture, params, loc); - Ok(FloatTexture::Wrinkled(tex)) - } - "windy" => { - let tex = WindyTexture::create(render_from_texture, params, loc); - Ok(FloatTexture::Windy(tex)) - } - "ptex" => { - let tex = FloatPtexTexture::create(render_from_texture, params, loc); - Ok(FloatTexture::Ptex(tex)) - } - _ => Err(format!("Float texture type '{}' unknown at {}", name, loc)), + "bilerp" => FloatBilerpTexture::create(render_from_texture, params, loc), + "imagemap" => FloatImageTexture::create(render_from_texture, params, loc), + "checkerboard" => FloatCheckerboardTexture::create(render_from_texture, params, loc), + "dots" => FloatDotsTexture::create(render_from_texture, params, loc), + "fbm" => FBmTexture::create(render_from_texture, params, loc), + "wrinkled" => WrinkledTexture::create(render_from_texture, params, loc), + "windy" => WindyTexture::create(render_from_texture, params, loc), + _ => Err(anyhow!("Float texture type '{}' unknown at {}", name, loc)), } } } @@ -129,12 +101,70 @@ pub enum SpectrumTexture { Scaled(SpectrumScaledTexture), } +pub trait CreateSpectrumTexture { + fn create( + render_from_texture: Transform, + parameters: TextureParameterDictionary, + spectrum_type: SpectrumType, + loc: FileLoc, + ) -> Result; +} + +impl SpectrumTexture { + pub fn create( + name: &str, + render_from_texture: Transform, + params: TextureParameterDictionary, + spectrum_type: SpectrumType, + loc: FileLoc, + _arena: &mut Arena, + ) -> Result { + match name { + "constant" => { + SpectrumConstantTexture::create(render_from_texture, params, spectrum_type, loc) + } + "scale" => { + SpectrumScaledTexture::create(render_from_texture, params, spectrum_type, loc) + } + "mix" => SpectrumMixTexture::create(render_from_texture, params, spectrum_type, loc), + "directionmix" => { + SpectrumDirectionMixTexture::create(render_from_texture, params, spectrum_type, loc) + } + "bilerp" => { + SpectrumBilerpTexture::create(render_from_texture, params, spectrum_type, loc) + } + "imagemap" => { + SpectrumImageTexture::create(render_from_texture, params, spectrum_type, loc) + } + "checkerboard" => { + SpectrumCheckerboardTexture::create(render_from_texture, params, spectrum_type, loc) + } + "dots" => SpectrumDotsTexture::create(render_from_texture, params, spectrum_type, loc), + _ => Err(anyhow!( + "Spectrum texture type '{}' unknown at {}", + name, + loc + )), + } + } +} + impl SpectrumTextureTrait for Arc { fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum { self.as_ref().evaluate(ctx, lambda) } } +impl SpectrumTextureTrait for SpectrumDotsTexture { + fn evaluate( + &self, + _ctx: &shared::core::texture::TextureEvalContext, + _lambda: &shared::spectra::SampledWavelengths, + ) -> shared::spectra::SampledSpectrum { + todo!() + } +} + pub trait CreateTextureMapping { fn create( params: &TextureParameterDictionary, diff --git a/src/filters/lanczos.rs b/src/filters/lanczos.rs index 5f8ab8b..54cd070 100644 --- a/src/filters/lanczos.rs +++ b/src/filters/lanczos.rs @@ -1,3 +1,4 @@ +use crate::core::filter::CreateFilterSampler; use shared::Float; use shared::core::filter::FilterSampler; use shared::core::geometry::{Point2f, Vector2f}; diff --git a/src/filters/mitchell.rs b/src/filters/mitchell.rs index 4334760..56b9c1b 100644 --- a/src/filters/mitchell.rs +++ b/src/filters/mitchell.rs @@ -1,3 +1,4 @@ +use crate::core::filter::CreateFilterSampler; use shared::Float; use shared::core::filter::FilterSampler; use shared::core::geometry::{Point2f, Vector2f}; diff --git a/src/globals.rs b/src/globals.rs index 0f0093e..a88c7ee 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -57,16 +57,16 @@ pub static REC2020_COEFFS: Lazy<&[Float]> = }); pub static SRGB_TABLE: Lazy = - Lazy::new(|| RGBToSpectrumTableData::new(SRGB_SCALE, SRGB_COEFFS)); + Lazy::new(|| RGBToSpectrumTableData::new(SRGB_SCALE.to_vec(), SRGB_COEFFS.to_vec())); pub static DCI_P3_TABLE: Lazy = - Lazy::new(|| RGBToSpectrumTableData::new(DCI_P3_SCALE, DCI_P3_COEFFS)); + Lazy::new(|| RGBToSpectrumTableData::new(DCI_P3_SCALE.to_vec(), DCI_P3_COEFFS.to_vec())); pub static REC2020_TABLE: Lazy = - Lazy::new(|| RGBToSpectrumTableData::new(REC2020_SCALE, REC2020_COEFFS)); + Lazy::new(|| RGBToSpectrumTableData::new(REC2020_SCALE.to_vec(), REC2020_COEFFS.to_vec())); pub static ACES_TABLE: Lazy = - Lazy::new(|| RGBToSpectrumTableData::new(ACES_SCALE, ACES_COEFFS)); + Lazy::new(|| RGBToSpectrumTableData::new(ACES_SCALE.to_vec(), ACES_COEFFS.to_vec())); // pub static ACES_TABLE: Lazy = Lazy::new(|| { // RGBToSpectrumTableData::load(Path::new("data/"), "aces2065_1") diff --git a/src/integrators/base.rs b/src/integrators/base.rs index c0687a4..9a950ba 100644 --- a/src/integrators/base.rs +++ b/src/integrators/base.rs @@ -2,7 +2,7 @@ use super::state::PathState; use crate::core::light::Light; use shared::core::geometry::Ray; use shared::core::interaction::{Interaction, InteractionTrait}; -use shared::core::primitive::Primitive; +use shared::core::primitive::{Primitive, PrimitiveTrait}; use shared::core::shape::ShapeIntersection; use shared::lights::LightSampler; use shared::spectra::SampledWavelengths; @@ -41,7 +41,10 @@ impl IntegratorBase { } pub fn unoccluded(&self, p0: &Interaction, p1: &Interaction) -> bool { - !self.intersect_p(&p0.spawn_ray_to_interaction(p1), Some(1. - SHADOW_EPSILON)) + !self.intersect_p( + &p0.spawn_ray_to_interaction(*p1.get_common()), + Some(1. - SHADOW_EPSILON), + ) } pub fn add_infinite_light_contribution( diff --git a/src/integrators/constants.rs b/src/integrators/constants.rs index 67f66d8..8957c6f 100644 --- a/src/integrators/constants.rs +++ b/src/integrators/constants.rs @@ -1,5 +1,5 @@ -use shared::core::geometry::Point2f; use shared::Float; +use shared::core::geometry::Point2f; pub const N_RHO_SAMPLES: usize = 16; @@ -23,20 +23,20 @@ pub static UC_RHO: [Float; N_RHO_SAMPLES] = [ ]; pub static U_RHO: [Point2f; N_RHO_SAMPLES] = [ - Point2f::new(0.855985, 0.570367), - Point2f::new(0.381823, 0.851844), - Point2f::new(0.285328, 0.764262), - Point2f::new(0.733380, 0.114073), - Point2f::new(0.542663, 0.344465), - Point2f::new(0.127274, 0.414848), - Point2f::new(0.964700, 0.947162), - Point2f::new(0.594089, 0.643463), - Point2f::new(0.095109, 0.170369), - Point2f::new(0.825444, 0.263359), - Point2f::new(0.429467, 0.454469), - Point2f::new(0.244460, 0.816459), - Point2f::new(0.756135, 0.731258), - Point2f::new(0.516165, 0.152852), - Point2f::new(0.180888, 0.214174), - Point2f::new(0.898579, 0.503897), + Point2f { 0: [0.855985, 0.570367]}, + Point2f { 0: [0.381823, 0.851844]}, + Point2f { 0: [0.285328, 0.764262]}, + Point2f { 0: [0.733380, 0.114073]}, + Point2f { 0: [0.542663, 0.344465]}, + Point2f { 0: [0.127274, 0.414848]}, + Point2f { 0: [0.964700, 0.947162]}, + Point2f { 0: [0.594089, 0.643463]}, + Point2f { 0: [0.095109, 0.170369]}, + Point2f { 0: [0.825444, 0.263359]}, + Point2f { 0: [0.429467, 0.454469]}, + Point2f { 0: [0.244460, 0.816459]}, + Point2f { 0: [0.756135, 0.731258]}, + Point2f { 0: [0.516165, 0.152852]}, + Point2f { 0: [0.180888, 0.214174]}, + Point2f { 0: [0.898579, 0.503897]}, ]; diff --git a/src/integrators/path.rs b/src/integrators/path.rs index c937e31..2cd5994 100644 --- a/src/integrators/path.rs +++ b/src/integrators/path.rs @@ -9,6 +9,7 @@ use shared::core::camera::Camera; use shared::core::film::VisibleSurface; use shared::core::geometry::{Point2i, Ray, Vector3f, VectorLike}; use shared::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; +use shared::core::light::LightTrait; use shared::core::light::{Light, LightSampleContext}; use shared::core::primitive::Primitive; use shared::core::sampler::{Sampler, SamplerTrait}; @@ -92,7 +93,7 @@ impl PathIntegrator { &self, intr: &SurfaceInteraction, bsdf: &BSDF, - state: &PathState, + _state: &PathState, lambda: &SampledWavelengths, sampler: &mut Sampler, ) -> SampledSpectrum { @@ -102,34 +103,34 @@ impl PathIntegrator { .light_sampler .sample_with_context(&ctx, sampler.get1d()) else { - return SampledSpectrum::ZERO; + return SampledSpectrum::zero(); }; let Some(ls) = sampled.light.sample_li(&ctx, sampler.get2d(), lambda, true) else { - return SampledSpectrum::ZERO; + return SampledSpectrum::zero(); }; if ls.l.is_black() || ls.pdf == 0.0 { - return SampledSpectrum::ZERO; + return SampledSpectrum::zero(); } let wo = intr.wo(); let wi = ls.wi; let Some(f) = bsdf.f(wo, wi, TransportMode::Radiance) else { - return SampledSpectrum::ZERO; + return SampledSpectrum::zero(); }; let f = f * wi.abs_dot(intr.shading.n.into()); if f.is_black() { - return SampledSpectrum::ZERO; + return SampledSpectrum::zero(); } if !self .base .unoccluded(&Interaction::Surface(intr.clone()), &ls.p_light) { - return SampledSpectrum::ZERO; + return SampledSpectrum::zero(); } let p_l = sampled.p * ls.pdf; @@ -233,7 +234,8 @@ impl RayIntegratorTrait for PathIntegrator { if state.depth == 0 || state.specular_bounce { state.l += state.beta * le; } else if self.config.use_mis { - if let Some(light) = &isect.area_light { + if !isect.area_light.is_null() { + let light = &isect.area_light; let p_l = self.light_sampler.pmf_with_context(&state.prev_ctx, light) * light.pdf_li(&state.prev_ctx, ray.d, true); let w_b = power_heuristic(1, state.prev_pdf, 1, p_l); diff --git a/src/integrators/pipeline.rs b/src/integrators/pipeline.rs index 0f652c6..72f7584 100644 --- a/src/integrators/pipeline.rs +++ b/src/integrators/pipeline.rs @@ -1,15 +1,18 @@ use super::RayIntegratorTrait; use super::base::IntegratorBase; use crate::Arena; -use crate::core::image::{Image, ImageMetadata}; +use crate::core::camera::InitMetadata; +use crate::core::film::FilmTrait; +use crate::core::image::{Image, ImageIO, ImageMetadata}; use crate::spectra::get_spectra_context; use indicatif::{ProgressBar, ProgressStyle}; +use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; use shared::Float; -use shared::core::camera::Camera; -use shared::core::geometry::{Bounds2i, Point2i}; +use shared::core::camera::{Camera, CameraTrait}; +use shared::core::geometry::{Bounds2i, Point2i, VectorLike}; use shared::core::options::get_options; -use shared::core::sampler::Sampler; use shared::core::sampler::get_camera_sample; +use shared::core::sampler::{Sampler, SamplerTrait}; use shared::spectra::SampledSpectrum; use std::io::Write; use std::path::Path; @@ -83,7 +86,7 @@ pub fn render( let s_index = sample_index as usize; let mut tile_sampler = sampler_prototype.clone(); - tile_sampler.start_pixel_sample(p_pixel, s_index, None); + tile_sampler.start_pixel_sample(p_pixel, s_index as i32, None); evaluate_pixel_sample( integrator, @@ -163,7 +166,7 @@ pub fn render( camera, &mut sampler, *p_pixel, - sample_index, + sample_index.try_into().unwrap(), arena, ); } @@ -202,7 +205,8 @@ pub fn render( let splat_scale = 1.0 / (wave_start as Float); let film_metadata = ImageMetadata::default(); - let film_image = camera.get_film().get_image(&film_metadata, splat_scale); + let film = *camera.get_film(); + let film_image = film.get_image(&film_metadata, splat_scale); let (mse_values, _mse_debug_img) = film_image.mse(film_image.all_channels_desc(), ref_img, false); diff --git a/src/integrators/state.rs b/src/integrators/state.rs index 0dfdcf9..e76d157 100644 --- a/src/integrators/state.rs +++ b/src/integrators/state.rs @@ -11,6 +11,7 @@ pub struct PathState { pub any_non_specular_bounces: bool, pub eta_scale: Float, pub prev_ctx: LightSampleContext, + pub prev_pdf: Float, } impl PathState { @@ -23,6 +24,7 @@ impl PathState { any_non_specular_bounces: false, eta_scale: 1.0, prev_ctx: LightSampleContext::default(), + prev_pdf: 1.0, } } diff --git a/src/lights/diffuse.rs b/src/lights/diffuse.rs index ebd5851..394df07 100644 --- a/src/lights/diffuse.rs +++ b/src/lights/diffuse.rs @@ -47,8 +47,8 @@ impl CreateDiffuseLight for DiffuseAreaLight { two_sided: bool, fov: Float, ) -> Self { - let is_constant_zero = match &alpha { - FloatTexture::Constant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0, + let is_constant_zero = match &*alpha { + GPUFloatTexture::Constant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0, _ => false, }; @@ -60,7 +60,7 @@ impl CreateDiffuseLight for DiffuseAreaLight { let base = LightBase::new(light_type, render_from_light, medium_interface); - let lemit = Ptr::from(&lookup_spectrum(&le)); + let lemit = lookup_spectrum(&le); if !image.is_null() { let desc = image @@ -96,8 +96,8 @@ impl CreateDiffuseLight for DiffuseAreaLight { image, colorspace, shape, - alpha: stored_alpha, - lemit, + alpha: stored_alpha.expect("Could not retrieve texture"), + lemit: Ptr::from(&*lemit), two_sided, scale, } diff --git a/src/lights/distant.rs b/src/lights/distant.rs index 0c86c26..8624228 100644 --- a/src/lights/distant.rs +++ b/src/lights/distant.rs @@ -25,10 +25,10 @@ impl CreateDistantLight for DistantLight { render_from_light, MediumInterface::empty(), ); - let lemit = Ptr::from(&lookup_spectrum(&le)); + let lemit = lookup_spectrum(&le); Self { base, - lemit, + lemit: Ptr::from(&*lemit), scale, scene_center: Point3f::default(), scene_radius: 0., diff --git a/src/lights/goniometric.rs b/src/lights/goniometric.rs index 11ab95d..295b57b 100644 --- a/src/lights/goniometric.rs +++ b/src/lights/goniometric.rs @@ -6,7 +6,7 @@ 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 anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use shared::core::geometry::Point2i; use shared::core::light::{Light, LightBase, LightType}; use shared::core::medium::{Medium, MediumInterface}; @@ -47,7 +47,7 @@ impl CreateGoniometricLight for GoniometricLight { let distrib = PiecewiseConstant2D::from_image(&image); Self { base, - iemit, + iemit: *iemit, scale, image: Ptr::from(image.device_image()), distrib: Ptr::from(&distrib.device), @@ -92,7 +92,7 @@ impl CreateLight for GoniometricLight { )); } - if res.x != res.y { + if res.x() != res.y() { return Err(anyhow!( loc, "image resolution ({}, {}) is non-square; unlikely to be an equal-area map", diff --git a/src/lights/infinite.rs b/src/lights/infinite.rs index 9d6f1a1..9d017cf 100644 --- a/src/lights/infinite.rs +++ b/src/lights/infinite.rs @@ -1,16 +1,15 @@ use crate::Arena; use crate::core::image::{Image, ImageIO}; +use crate::core::light::lookup_spectrum; use crate::core::spectrum::spectrum_to_photometric; use crate::spectra::get_spectra_context; use crate::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D}; -use crate::utils::{FileLoc, ParameterDictionary, resolve_filename}; +use crate::utils::{FileLoc, ParameterDictionary, Upload, resolve_filename}; use anyhow::{Result, anyhow}; - -use rayon::iter::{IndexedParallelIterator, ParallelIterator}; -use rayon::prelude::ParallelSliceMut; +use rayon::prelude::*; use shared::core::camera::CameraTransform; use shared::core::geometry::{Bounds2f, Frame, Point2f, Point2i, Point3f, VectorLike, cos_theta}; -use shared::core::image::{DeviceImage, PixelFormat, WrapMode}; +use shared::core::image::{PixelFormat, WrapMode}; use shared::core::light::{Light, LightBase, LightType}; use shared::core::medium::MediumInterface; use shared::core::spectrum::Spectrum; @@ -18,116 +17,58 @@ use shared::core::texture::SpectrumType; use shared::lights::{ImageInfiniteLight, PortalInfiniteLight, UniformInfiniteLight}; use shared::spectra::RGBColorSpace; use shared::utils::math::{equal_area_sphere_to_square, equal_area_square_to_sphere}; +use shared::utils::sampling::{DevicePiecewiseConstant2D, DeviceWindowedPiecewiseConstant2D}; use shared::utils::{Ptr, Transform}; use shared::{Float, PI}; use std::path::Path; -use std::sync::Arc; - -use crate::core::light::lookup_spectrum; pub trait CreateImageInfiniteLight { fn new( render_from_light: Transform, - medium_interface: MediumInterface, scale: Float, - image: Arc, - image_color_space: Arc, + image: Ptr, + image_color_space: Ptr, + distrib: Ptr, + compensated_distrib: Ptr, ) -> Self; } impl CreateImageInfiniteLight for ImageInfiniteLight { fn new( render_from_light: Transform, - medium_interface: MediumInterface, scale: Float, - image: Arc, - image_color_space: Arc, + image: Ptr, + image_color_space: Ptr, + distrib: Ptr, + compensated_distrib: Ptr, ) -> Self { let base = LightBase::new( LightType::Infinite, render_from_light, MediumInterface::default(), ); - - let desc = image - .get_channel_desc(&["R", "G", "B"]) - .expect("Image used for DiffuseAreaLight doesn't have R, G, B channels"); - - assert_eq!(3, desc.size()); - assert!(desc.is_identity()); - - 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 = distrib.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; - } - - if all_zero { - data.fill(1.0); - } - - let compensated_distrib = PiecewiseConstant2D::new(&data, n_u, n_v); - - ImageInfiniteLight { + Self { base, - image: Ptr::from(image.device_image()), - image_color_space: Ptr::from(image_color_space.as_ref()), - scene_center: Point3f::default(), - scene_radius: 0., + image, + image_color_space, scale, - distrib: Ptr::from(&*distrib), - compensated_distrib: Ptr::from(&*compensated_distrib), + distrib, + compensated_distrib, + scene_center: Point3f::default(), + scene_radius: 0.0, } } } -#[derive(Debug)] -struct InfinitePortalLightStorage { - image: Image, - distribution: WindowedPiecewiseConstant2D, - image_color_space: RGBColorSpace, -} - -#[derive(Clone, Debug)] -pub struct PortalInfiniteLightHost { - pub device: PortalInfiniteLight, - pub filename: String, - _storage: Arc, -} - pub trait CreatePortalInfiniteLight { fn new( render_from_light: Transform, scale: Float, - image: Arc, - image_color_space: Arc, - points: Vec, + image: Ptr, + image_color_space: Ptr, + portal: [Point3f; 4], + portal_frame: Frame, + distribution: Ptr, ) -> Self; } @@ -135,145 +76,48 @@ impl CreatePortalInfiniteLight for PortalInfiniteLight { fn new( render_from_light: Transform, scale: Float, - image: Arc, - image_color_space: Arc, - points: Vec, + image: Ptr, + image_color_space: Ptr, + portal: [Point3f; 4], + portal_frame: Frame, + distribution: Ptr, ) -> Self { let base = LightBase::new( LightType::Infinite, render_from_light, MediumInterface::default(), ); - - let desc = image - .get_channel_desc(&["R", "G", "B"]) - .unwrap_or_else(|_| { - panic!("Image used for PortalImageInfiniteLight doesn't have R, G, B channels.",) - }); - - assert_eq!(3, desc.offset.len()); - 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.", - src_res.x(), - src_res.y() - ); - } - - if points.len() != 4 { - panic!( - "Expected 4 vertices for infinite light portal but given {}", - points.len() - ); - } - - let portal: [Point3f; 4] = [points[0], points[1], points[2], points[3]]; - - let p01 = (portal[1] - portal[0]).normalize(); - let p12 = (portal[2] - portal[1]).normalize(); - let p32 = (portal[2] - portal[3]).normalize(); - let p03 = (portal[3] - portal[0]).normalize(); - - if (p01.dot(p32) - 1.0).abs() > 0.001 || (p12.dot(p03) - 1.0).abs() > 0.001 { - panic!("Infinite light portal isn't a planar quadrilateral (opposite edges)"); - } - - if p01.dot(p12).abs() > 0.001 - || p12.dot(p32).abs() > 0.001 - || p32.dot(p03).abs() > 0.001 - || p03.dot(p01).abs() > 0.001 - { - panic!("Infinite light portal isn't a planar quadrilateral (perpendicular edges)"); - } - - let portal_frame = Frame::from_xy(p03, p01); - - let width = src_res.x(); - let height = src_res.y(); - - let mut new_pixels = vec![0.0 as Float; (width * height * 3) as usize]; - - new_pixels - .par_chunks_mut((width * 3) as usize) - .enumerate() - .for_each(|(y, row_pixels)| { - let y = y as i32; - - for x in 0..width { - let uv = Point2f::new( - (x as Float + 0.5) / width as Float, - (y as Float + 0.5) / height as Float, - ); - - let (w_world, _) = Self::render_from_image(portal_frame, uv); - let w_local = render_from_light.apply_inverse_vector(w_world).normalize(); - let uv_equi = equal_area_sphere_to_square(w_local); - - let pixel_idx = (x * 3) as usize; - - for c in 0..3 { - let val = image.bilerp_channel_with_wrap( - uv_equi, - c, - WrapMode::OctahedralSphere.into(), - ); - row_pixels[pixel_idx + c as usize] = val; - } - } - }); - - 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); - jacobian - }; - - let d = img.get_sampling_distribution( - duv_dw_closure, - Bounds2f::from_points(Point2f::new(0., 0.), Point2f::new(1., 1.)), - ); - - let distribution = WindowedPiecewiseConstant2D::new(d); - - PortalInfiniteLight { + Self { base, - image: Ptr::from(img.device_image()), - image_color_space: Ptr::from(&*image_color_space), + image, + image_color_space, scale, - scene_center: Point3f::default(), - scene_radius: 0., portal, portal_frame, - distribution, + distribution: *distribution, + scene_center: Point3f::default(), + scene_radius: 0.0, } } } pub trait CreateUniformInfiniteLight { - fn new(render_from_light: Transform, le: Spectrum, scale: Float) -> Self; + fn new(render_from_light: Transform, scale: Float, lemit: Ptr) -> Self; } impl CreateUniformInfiniteLight for UniformInfiniteLight { - fn new(render_from_light: Transform, le: Spectrum, scale: Float) -> Self { + fn new(render_from_light: Transform, scale: Float, lemit: Ptr) -> Self { let base = LightBase::new( LightType::Infinite, render_from_light, MediumInterface::default(), ); - let lemit = Ptr::from(&lookup_spectrum(&le)); Self { base, lemit, scale, scene_center: Point3f::default(), - scene_radius: 0., + scene_radius: 0.0, } } } @@ -281,7 +125,7 @@ impl CreateUniformInfiniteLight for UniformInfiniteLight { pub fn create( arena: &mut Arena, render_from_light: Transform, - medium: MediumInterface, + _medium: MediumInterface, camera_transform: CameraTransform, parameters: &ParameterDictionary, colorspace: &RGBColorSpace, @@ -301,6 +145,7 @@ pub fn create( return Err(anyhow!(loc, "cannot specify both \"L\" and \"filename\"")); } + // Uniform infinite light (no image) if !has_file && !has_portal { let spectrum = if has_spectrum { scale /= spectrum_to_photometric(l[0]); @@ -313,13 +158,13 @@ pub fn create( scale *= e_v / PI; } - let light = UniformInfiniteLight::new(render_from_light, spectrum, scale); + let lemit = lookup_spectrum(&spectrum); + let light = UniformInfiniteLight::new(render_from_light, scale, lemit.upload(arena)); return Ok(Light::InfiniteUniform(light)); } - // Image based - - let (image, image_cs) = load_image_or_constant(&filename, &l, colorspace, loc)?; + // Image-based lights + let (image, image_cs) = load_image(&filename, &l, colorspace, loc)?; scale /= spectrum_to_photometric(Spectrum::Dense(image_cs.illuminant)); @@ -328,26 +173,194 @@ pub fn create( scale *= e_v / k_e; } - // 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.camera_from_world(0.0).apply_to_point(*p)) - .collect(); - - 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)) + create_portal_light( + arena, + render_from_light, + scale, + image, + image_cs, + &portal, + camera_transform, + loc, + ) } else { - let light = ImageInfiniteLight::new(render_from_light, medium, scale, image, cs); - Ok(Light::InfiniteImage(light)) + create_image_light(arena, render_from_light, scale, image, image_cs) } } -fn load_image_or_constant( +fn create_image_light( + arena: &mut Arena, + render_from_light: Transform, + scale: Float, + image: Image, + image_cs: RGBColorSpace, +) -> Result { + let res = image.resolution(); + assert_eq!( + res.x(), + res.y(), + "Image must be square for equal-area mapping" + ); + + let (n_u, n_v) = (res.x() as usize, res.y() as usize); + + // Extract luminance data + 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); + + // Build compensated distribution + let average = data.iter().sum::() / data.len() as Float; + let mut all_zero = true; + for v in &mut data { + *v = (*v - average).max(0.0); + all_zero &= *v == 0.0; + } + if all_zero { + data.fill(1.0); + } + let compensated_distrib = PiecewiseConstant2D::new(&data, n_u, n_v); + + let light = ImageInfiniteLight::new( + render_from_light, + scale, + image.upload(arena), + image_cs.upload(arena), + distrib.upload(arena), + compensated_distrib.upload(arena), + ); + + Ok(Light::InfiniteImage(light)) +} + +fn create_portal_light( + arena: &mut Arena, + render_from_light: Transform, + scale: Float, + image: Image, + image_cs: RGBColorSpace, + portal_points: &[Point3f], + camera_transform: CameraTransform, + loc: &FileLoc, +) -> Result { + let res = image.resolution(); + if res.x() != res.y() { + return Err(anyhow!(loc, "Portal light image must be square")); + } + + // Validate portal + if portal_points.len() != 4 { + return Err(anyhow!( + loc, + "Portal requires exactly 4 vertices, got {}", + portal_points.len() + )); + } + + let portal: [Point3f; 4] = portal_points + .iter() + .map(|p| camera_transform.camera_from_world(0.0).apply_to_point(*p)) + .collect::>() + .try_into() + .unwrap(); + + let portal_frame = validate_and_build_portal_frame(&portal, loc)?; + + // Remap image through portal + let remapped = remap_image_through_portal(&image, &render_from_light, &portal_frame); + + // Build distribution + let duv_dw = |p: Point2f| -> Float { + let (_, jacobian) = PortalInfiniteLight::render_from_image(portal_frame, p); + jacobian + }; + let d = remapped.get_sampling_distribution( + duv_dw, + Bounds2f::from_points(Point2f::zero(), Point2f::fill(1.)), + ); + let distribution = WindowedPiecewiseConstant2D::new(d); + + let light = PortalInfiniteLight::new( + render_from_light, + scale, + remapped.upload(arena), + image_cs.upload(arena), + portal, + portal_frame, + distribution.upload(arena), + ); + + Ok(Light::InfinitePortal(light)) +} + +fn validate_and_build_portal_frame(portal: &[Point3f; 4], loc: &FileLoc) -> Result { + let p01 = (portal[1] - portal[0]).normalize(); + let p12 = (portal[2] - portal[1]).normalize(); + let p32 = (portal[2] - portal[3]).normalize(); + let p03 = (portal[3] - portal[0]).normalize(); + + if (p01.dot(p32) - 1.0).abs() > 0.001 || (p12.dot(p03) - 1.0).abs() > 0.001 { + return Err(anyhow!(loc, "Portal edges not parallel")); + } + + if p01.dot(p12).abs() > 0.001 + || p12.dot(p32).abs() > 0.001 + || p32.dot(p03).abs() > 0.001 + || p03.dot(p01).abs() > 0.001 + { + return Err(anyhow!(loc, "Portal edges not perpendicular")); + } + + Ok(Frame::from_xy(p03, p01)) +} + +fn remap_image_through_portal( + image: &Image, + render_from_light: &Transform, + portal_frame: &Frame, +) -> Image { + let res = image.resolution(); + let (width, height) = (res.x() as usize, res.y() as usize); + + let mut pixels = vec![0.0f32; width * height * 3]; + + pixels + .par_chunks_mut(width * 3) + .enumerate() + .for_each(|(y, row)| { + for x in 0..width { + let uv = Point2f::new( + (x as Float + 0.5) / width as Float, + (y as Float + 0.5) / height as Float, + ); + + let (w_world, _) = PortalInfiniteLight::render_from_image(*portal_frame, uv); + let w_local = render_from_light.apply_inverse_vector(w_world).normalize(); + let uv_equi = equal_area_sphere_to_square(w_local); + + for c in 0..3 { + row[x * 3 + c] = image.bilerp_channel_with_wrap( + uv_equi, + c as i32, + WrapMode::OctahedralSphere.into(), + ); + } + } + }); + + Image::from_f32(pixels, res, &["R", "G", "B"]) +} + +fn load_image( filename: &str, l: &[Spectrum], colorspace: &RGBColorSpace, @@ -355,52 +368,51 @@ fn load_image_or_constant( ) -> Result<(Image, RGBColorSpace)> { if filename.is_empty() { 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(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(anyhow!(loc, "image '{}' has invalid pixels", filename)); - } - - im.image - .get_channel_desc(&["R", "G", "B"]) - .map_err(|_| anyhow!(loc, "image '{}' must have R, G, B channels", filename))?; - - 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)) + let rgb = l[0].to_rgb(colorspace, &stdspec); + let image = + Image::new_constant(Point2i::new(1, 1), &["R", "G", "B"], &[rgb.r, rgb.g, rgb.b]); + return Ok((image, colorspace.clone())); } + + 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(anyhow!(loc, "image '{}' has invalid pixels", filename)); + } + + let desc = im + .image + .get_channel_desc(&["R", "G", "B"]) + .map_err(|_| anyhow!(loc, "image '{}' must have R, G, B channels", filename))?; + + let cs = im.metadata.colorspace.unwrap_or_else(|| colorspace.clone()); + Ok((im.image.select_channels(&desc), cs)) } fn compute_hemisphere_illuminance(image: &Image, cs: &RGBColorSpace) -> Float { let lum = cs.luminance_vector(); 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 sum: Float = (0..res.y()) + .flat_map(|y| (0..res.x()).map(move |x| (x, y))) + .filter_map(|(x, y)| { let u = (x as Float + 0.5) / res.x() as Float; + let v = (y as Float + 0.5) / res.y() as Float; let w = equal_area_square_to_sphere(Point2f::new(u, v)); if w.z() <= 0.0 { - continue; + return None; } - 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); + let p = Point2i::new(x, y); + let r = image.get_channel(p, 0); + let g = image.get_channel(p, 1); + let b = image.get_channel(p, 2); - sum += (r * lum[0] + g * lum[1] + b * lum[2]) * cos_theta(w); - } - } + Some((r * lum[0] + g * lum[1] + b * lum[2]) * cos_theta(w)) + }) + .sum(); sum * 2.0 * PI / (res.x() * res.y()) as Float } diff --git a/src/lights/point.rs b/src/lights/point.rs index 9ebf218..88f4fab 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 = Ptr::from(lookup_spectrum(&le)); + let i = Ptr::from(&*lookup_spectrum(&le)); Self { base, scale, i } } diff --git a/src/lights/projection.rs b/src/lights/projection.rs index 41ec773..d8c25ee 100644 --- a/src/lights/projection.rs +++ b/src/lights/projection.rs @@ -27,6 +27,7 @@ pub trait CreateProjectionLight { scale: Float, image: Ptr, image_color_space: Ptr, + distrib: Ptr, fov: Float, ) -> Self; } @@ -38,6 +39,7 @@ impl CreateProjectionLight for ProjectionLight { scale: Float, image: Ptr, image_color_space: Ptr, + distrib: Ptr, fov: Float, ) -> Self { let base = LightBase::new( @@ -45,30 +47,25 @@ impl CreateProjectionLight for ProjectionLight { render_from_light, medium_interface, ); - let aspect = image.resolution().x() as Float / image.resolution().y() as Float; - let screen_bounds = if aspect > 1. { - Bounds2f::from_points(Point2f::new(-aspect, -1.), Point2f::new(aspect, 1.)) + + let res = image.resolution(); + let aspect = res.x() as Float / res.y() as Float; + let screen_bounds = if aspect > 1.0 { + Bounds2f::from_points(Point2f::new(-aspect, -1.0), Point2f::new(aspect, 1.0)) } else { Bounds2f::from_points( - Point2f::new(-1., 1. / aspect), - Point2f::new(1., 1. / aspect), + Point2f::new(-1.0, -1.0 / aspect), + Point2f::new(1.0, 1.0 / aspect), ) }; let hither = 1e-3; let screen_from_light = Transform::perspective(fov, hither, 1e30).unwrap(); let light_from_screen = screen_from_light.inverse(); - 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| { - let w = - Vector3f::from(light_from_screen.apply_to_point(Point3f::new(p.x(), p.y(), 0.))); - cos_theta(w.normalize()).powi(3) - }; - let d = image.get_sampling_distribution(dwda, screen_bounds); - let distrib = Ptr::from(&PiecewiseConstant2D::from_image(image).device); + let opposite = (radians(fov) / 2.0).tan(); + let aspect_ratio = if aspect > 1.0 { aspect } else { 1.0 / aspect }; + let a = 4.0 * square(opposite) * aspect_ratio; Self { base, @@ -143,12 +140,23 @@ impl CreateLight for ProjectionLight { let flip = Transform::scale(1., -1., 1.); let render_from_light_flip = render_from_light * flip; + let dwda = |p: Point2f| { + let w = + Vector3f::from(light_from_screen.apply_to_point(Point3f::new(p.x(), p.y(), 0.0))); + cos_theta(w.normalize()).powi(3) + }; + + let d = image.get_sampling_distribution(dwda, screen_bounds); + let distrib = + PiecewiseConstant2D::new(d.as_slice(), d.x_size() as usize, d.y_size() as usize); + let specific = ProjectionLight::new( render_from_light_flip, medium.into(), scale, image.upload(arena), colorspace.upload(arena), + distrib.upload(arena), fov, ); diff --git a/src/lights/spot.rs b/src/lights/spot.rs index 051760d..d47bd95 100644 --- a/src/lights/spot.rs +++ b/src/lights/spot.rs @@ -42,7 +42,7 @@ impl CreateSpotLight for SpotLight { MediumInterface::empty(), ); - let iemit = Ptr::from(&lookup_spectrum(&le)); + let iemit = Ptr::from(&*lookup_spectrum(&le)); Self { base, iemit, diff --git a/src/materials/complex.rs b/src/materials/complex.rs index 61519ae..f04375b 100644 --- a/src/materials/complex.rs +++ b/src/materials/complex.rs @@ -1,6 +1,6 @@ use crate::core::image::Image; use crate::core::material::CreateMaterial; -use crate::spectra::get_colorspace_context; +use crate::spectra::{get_colorspace_context, get_colorspace_device}; use crate::utils::{Arena, FileLoc, TextureParameterDictionary, Upload}; use shared::bxdfs::HairBxDF; use shared::core::material::Material; @@ -33,10 +33,9 @@ impl CreateMaterial for HairMaterial { // Default distribution if nothing is spceified let sigma_a = if sigma_a.is_none() && !reflectance.is_none() && !has_melanin { - let stdcs = get_colorspace_context(); + let stdcs = get_colorspace_device(); let default_rgb = HairBxDF::sigma_a_from_concentration(1.3, 0.0, stdcs); - let spectrum = - Spectrum::RGBUnbounded(RGBUnboundedSpectrum::new(stdcs.srgb, default_rgb)); + let spectrum = Spectrum::RGBUnbounded(default_rgb); let texture = SpectrumTexture::Constant(SpectrumConstantTexture::new(spectrum)); Some(Arc::new(texture)) } else { @@ -73,3 +72,15 @@ impl CreateMaterial for SubsurfaceMaterial { todo!() } } + +impl CreateMaterial for MeasuredMaterial { + fn create( + _parameters: &TextureParameterDictionary, + _normal_map: Option>, + _named_materials: &HashMap, + _loc: &FileLoc, + _arena: &mut Arena, + ) -> Result { + todo!() + } +} diff --git a/src/materials/mod.rs b/src/materials/mod.rs index af7d13e..cd96446 100644 --- a/src/materials/mod.rs +++ b/src/materials/mod.rs @@ -3,4 +3,5 @@ pub mod complex; pub mod conductor; pub mod dielectric; pub mod diffuse; +pub mod measured; pub mod mix; diff --git a/src/samplers/mod.rs b/src/samplers/mod.rs index 7a7b1f7..88f599f 100644 --- a/src/samplers/mod.rs +++ b/src/samplers/mod.rs @@ -2,3 +2,8 @@ pub mod halton; pub mod independent; pub mod sobol; pub mod stratified; + +pub use halton::*; +pub use independent::*; +pub use sobol::*; +pub use stratified::*; diff --git a/src/shapes/bilinear.rs b/src/shapes/bilinear.rs index eab7a74..a69f8c9 100644 --- a/src/shapes/bilinear.rs +++ b/src/shapes/bilinear.rs @@ -4,6 +4,7 @@ use crate::core::texture::FloatTexture; use crate::shapes::mesh::BilinearPatchMesh; use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::{Arena, FileLoc, ParameterDictionary}; +use anyhow::Result; use log::warn; use shared::core::shape::Shape; use shared::shapes::BilinearPatchShape; @@ -18,10 +19,10 @@ impl CreateShape for BilinearPatchShape { _object_from_render: Transform, reverse_orientation: bool, parameters: ParameterDictionary, - _float_textures: HashMap, + _float_textures: HashMap>, _loc: FileLoc, _arena: &mut Arena, - ) -> Result, String> { + ) -> Result> { let mut vertex_indices = parameters.get_int_array("indices"); let p = parameters.get_point3f_array("P"); let mut uv = parameters.get_point2f_array("uv"); @@ -32,10 +33,9 @@ impl CreateShape for BilinearPatchShape { if p.len() == 4 { vertex_indices = vec![0, 1, 2, 3]; } else { - return Err( + return Err(anyhow!( "Vertex indices \"indices\" must be provided with bilinear patch mesh shape." - .into(), - ); + )); } } else if vertex_indices.len() % 4 != 0 { let excess = vertex_indices.len() % 4; @@ -49,9 +49,9 @@ impl CreateShape for BilinearPatchShape { } if p.is_empty() { - return Err( - "Vertex positions \"P\" must be provided with bilinear patch mesh shape.".into(), - ); + return Err(anyhow!( + "Vertex positions \"P\" must be provided with bilinear patch mesh shape." + )); } if !uv.is_empty() && uv.len() != p.len() { @@ -66,7 +66,7 @@ impl CreateShape for BilinearPatchShape { for (_, &idx) in vertex_indices.iter().enumerate() { if idx < 0 || idx as usize >= p.len() { - return Err(format!( + return Err(anyhow!( "Bilinear patch mesh has out-of-bounds vertex index {} ({} \"P\" values were given). Discarding this mesh.", idx, p.len() @@ -85,26 +85,22 @@ impl CreateShape for BilinearPatchShape { } let filename = parameters.get_one_string("emissionfilename", ""); - let mut image_dist = None; - if !filename.is_empty() { + let image_dist = if !filename.is_empty() { if !uv.is_empty() { warn!( "\"emissionfilename\" is currently ignored for bilinear patches if \"uv\" coordinates have been provided--sorry!" ); + None } else { - match Image::read(Path::new(&filename), None) { - Ok(mut im) => { - let mut img = im.image; - img.flip_y(); - image_dist = Some(PiecewiseConstant2D::from_image(&img)); - } - Err(e) => { - warn!("Failed to load emission image \"{}\": {}", filename, e); - } - } + let im = Image::read(Path::new(&filename), None)?; + let mut img = im.image; + img.flip_y(); + Some(PiecewiseConstant2D::from_image(&img)) } - } + } else { + None + }; let host = BilinearPatchMesh::new( &render_from_object, @@ -113,7 +109,7 @@ impl CreateShape for BilinearPatchShape { p, n, uv, - None, + image_dist, ); let host_arc = Arc::new(host); diff --git a/src/shapes/curves.rs b/src/shapes/curves.rs index 4481dfc..a521af3 100644 --- a/src/shapes/curves.rs +++ b/src/shapes/curves.rs @@ -2,6 +2,7 @@ use crate::Arena; use crate::core::shape::CreateShape; use crate::core::texture::FloatTexture; use crate::utils::{FileLoc, ParameterDictionary}; +use anyhow::{Result, anyhow}; use shared::Float; use shared::core::geometry::{Normal3f, Point3f}; use shared::core::shape::Shape; @@ -14,6 +15,7 @@ use shared::utils::splines::{ use log::warn; use std::collections::HashMap; +use std::sync::Arc; pub fn create_curve( render_from_object: Transform, @@ -60,17 +62,17 @@ impl CreateShape for CurveShape { object_from_render: Transform, reverse_orientation: bool, parameters: ParameterDictionary, - _float_textures: HashMap, + _float_textures: HashMap>, _loc: FileLoc, _arena: &mut Arena, - ) -> Result, String> { + ) -> Result> { let width = parameters.get_one_float("width", 1.0); let width0 = parameters.get_one_float("width0", width); let width1 = parameters.get_one_float("width1", width); let degree = parameters.get_one_int("degree", 3); if degree != 2 && degree != 3 { - return Err(format!( + return Err(anyhow!( "Invalid degree {}: only degree 2 and 3 curves are supported.", degree )); @@ -78,7 +80,7 @@ impl CreateShape for CurveShape { let basis = parameters.get_one_string("basis", "bezier"); if basis != "bezier" && basis != "bspline" { - return Err(format!( + return Err(anyhow!( "Invalid basis \"{}\": only \"bezier\" and \"bspline\" are supported.", basis )); @@ -91,7 +93,7 @@ impl CreateShape for CurveShape { if cp.len() <= degree as usize || ((cp.len() - 1 - degree as usize) % degree as usize) != 0 { - return Err(format!( + return Err(anyhow!( "Invalid number of control points {}: for the degree {} Bezier basis {} + n * {} are required.", cp.len(), degree, @@ -102,7 +104,7 @@ impl CreateShape for CurveShape { n_segments = (cp.len() - 1) / degree as usize; } else { if cp.len() < (degree + 1) as usize { - return Err(format!( + return Err(anyhow!( "Invalid number of control points {}: for the degree {} b-spline basis, must have >= {}.", cp.len(), degree, @@ -118,7 +120,7 @@ impl CreateShape for CurveShape { "ribbon" => CurveType::Ribbon, "cylinder" => CurveType::Cylinder, _ => { - return Err(format!("Unknown curve type \"{}\".", curve_type_str)); + return Err(anyhow!("Unknown curve type \"{}\".", curve_type_str)); } }; @@ -128,7 +130,7 @@ impl CreateShape for CurveShape { warn!("Curve normals are only used with \"ribbon\" type curves. Discarding."); n.clear(); } else if n.len() != n_segments + 1 { - return Err(format!( + return Err(anyhow!( "Invalid number of normals {}: must provide {} normals for ribbon curves with {} segments.", n.len(), n_segments + 1, @@ -136,9 +138,9 @@ impl CreateShape for CurveShape { )); } } else if curve_type == CurveType::Ribbon { - return Err( - "Must provide normals \"N\" at curve endpoints with ribbon curves.".to_string(), - ); + return Err(anyhow!( + "Must provide normals \"N\" at curve endpoints with ribbon curves." + )); } let use_gpu = false; // Replace with actual config check diff --git a/src/shapes/cylinder.rs b/src/shapes/cylinder.rs index 3834b08..9c800a3 100644 --- a/src/shapes/cylinder.rs +++ b/src/shapes/cylinder.rs @@ -1,10 +1,12 @@ use crate::core::shape::CreateShape; use crate::core::texture::FloatTexture; use crate::utils::{Arena, FileLoc, ParameterDictionary}; +use anyhow::Result; use shared::core::shape::Shape; use shared::shapes::CylinderShape; use shared::utils::Transform; use std::collections::HashMap; +use std::sync::Arc; impl CreateShape for CylinderShape { fn create( @@ -12,10 +14,10 @@ impl CreateShape for CylinderShape { object_from_render: Transform, reverse_orientation: bool, parameters: ParameterDictionary, - _float_textures: HashMap, + _float_textures: HashMap>, _loc: FileLoc, arena: &mut Arena, - ) -> Result, String> { + ) -> Result> { let radius = parameters.get_one_float("radius", 1.); let z_min = parameters.get_one_float("zmin", -1.); let z_max = parameters.get_one_float("zmax", 1.); diff --git a/src/shapes/disk.rs b/src/shapes/disk.rs index e21cd80..606e2b9 100644 --- a/src/shapes/disk.rs +++ b/src/shapes/disk.rs @@ -1,10 +1,12 @@ use crate::core::shape::CreateShape; use crate::core::texture::FloatTexture; use crate::utils::{Arena, FileLoc, ParameterDictionary}; +use anyhow::Result; use shared::core::shape::Shape; use shared::shapes::DiskShape; use shared::utils::Transform; use std::collections::HashMap; +use std::sync::Arc; impl CreateShape for DiskShape { fn create( @@ -12,10 +14,10 @@ impl CreateShape for DiskShape { object_from_render: Transform, reverse_orientation: bool, parameters: ParameterDictionary, - _float_textures: HashMap, + _float_textures: HashMap>, _loc: FileLoc, arena: &mut Arena, - ) -> Result, String> { + ) -> Result> { let height = parameters.get_one_float("height", 0.); let radius = parameters.get_one_float("radius", 1.); let inner_radius = parameters.get_one_float("innerradius", 0.); diff --git a/src/shapes/mod.rs b/src/shapes/mod.rs index 5985d43..775e45f 100644 --- a/src/shapes/mod.rs +++ b/src/shapes/mod.rs @@ -3,12 +3,16 @@ pub mod curves; pub mod cylinder; pub mod disk; pub mod mesh; -pub mod mesh; pub mod sphere; pub mod triangle; -// pub use bilinear::*; +pub use bilinear::*; +pub use curves::*; +pub use cylinder::*; +pub use disk::*; pub use mesh::*; +pub use sphere::*; +pub use triangle::*; use std::sync::{Arc, Mutex}; diff --git a/src/shapes/sphere.rs b/src/shapes/sphere.rs index 89cf43f..476847b 100644 --- a/src/shapes/sphere.rs +++ b/src/shapes/sphere.rs @@ -1,10 +1,12 @@ use crate::core::shape::CreateShape; use crate::core::texture::FloatTexture; use crate::utils::{Arena, FileLoc, ParameterDictionary}; +use anyhow::Result; use shared::core::shape::Shape; use shared::shapes::SphereShape; use shared::utils::Transform; use std::collections::HashMap; +use std::sync::Arc; impl CreateShape for SphereShape { fn create( @@ -12,10 +14,10 @@ impl CreateShape for SphereShape { object_from_render: Transform, reverse_orientation: bool, parameters: ParameterDictionary, - _float_textures: HashMap, + _float_textures: HashMap>, _loc: FileLoc, arena: &mut Arena, - ) -> Result, String> { + ) -> Result> { let radius = parameters.get_one_float("radius", 1.); let zmin = parameters.get_one_float("zmin", -radius); let zmax = parameters.get_one_float("zmax", radius); diff --git a/src/shapes/triangle.rs b/src/shapes/triangle.rs index 3f8a9b4..d1a7bd0 100644 --- a/src/shapes/triangle.rs +++ b/src/shapes/triangle.rs @@ -2,6 +2,7 @@ use crate::core::shape::{ALL_TRIANGLE_MESHES, CreateShape}; use crate::core::texture::FloatTexture; use crate::shapes::mesh::TriangleMesh; use crate::utils::{Arena, FileLoc, ParameterDictionary}; +use anyhow::{Result, anyhow}; use log::warn; use shared::core::shape::Shape; use shared::shapes::TriangleShape; @@ -15,10 +16,10 @@ impl CreateShape for TriangleShape { _object_from_render: Transform, reverse_orientation: bool, parameters: ParameterDictionary, - _float_texture: HashMap, + _float_texture: HashMap>, _loc: FileLoc, _arena: &mut Arena, - ) -> Result, String> { + ) -> Result> { let mut vertex_indices = parameters.get_int_array("indices"); let p = parameters.get_point3f_array("P"); let mut uvs = parameters.get_point2f_array("uv"); @@ -28,9 +29,9 @@ impl CreateShape for TriangleShape { if vertex_indices.is_empty() { if p.len() == 3 { } else { - return Err( - "Vertex indices \"indices\" must be provided with triangle mesh.".to_string(), - ); + return Err(anyhow!( + "Vertex indices \"indices\" must be provided with triangle mesh." + )); } } else if vertex_indices.len() % 3 != 0 { let excess = vertex_indices.len() % 3; @@ -44,7 +45,9 @@ impl CreateShape for TriangleShape { } if p.is_empty() { - return Err("Vertex positions \"P\" must be provided with triangle mesh.".to_string()); + return Err(anyhow!( + "Vertex positions \"P\" must be provided with triangle mesh." + )); } if !uvs.is_empty() && uvs.len() != p.len() { @@ -65,7 +68,7 @@ impl CreateShape for TriangleShape { for (_, &index) in vertex_indices.iter().enumerate() { // Check for negative indices (if keeping i32) or out of bounds if index < 0 || index as usize >= p.len() { - return Err(format!( + return Err(anyhow!( "TriangleMesh has out-of-bounds vertex index {} ({} \"P\" values were given). Discarding this mesh.", index, p.len() diff --git a/src/spectra/mod.rs b/src/spectra/mod.rs index a819a30..bfaa5ca 100644 --- a/src/spectra/mod.rs +++ b/src/spectra/mod.rs @@ -3,6 +3,7 @@ use crate::spectra::colorspace::RGBColorSpaceData; use shared::core::geometry::Point2f; use shared::core::spectrum::Spectrum; use shared::core::spectrum::StandardSpectra; +use shared::spectra::DeviceStandardColorSpaces; use shared::spectra::cie::{CIE_D65, CIE_X, CIE_Y, CIE_Z}; use shared::utils::Ptr; use std::sync::Arc; @@ -116,3 +117,12 @@ pub fn get_colorspace_context() -> StandardColorSpaces { aces2065_1: ACES.clone().into(), } } + +pub fn get_colorspace_device() -> DeviceStandardColorSpaces { + DeviceStandardColorSpaces { + srgb: Ptr::from(&SRGB.view), + dci_p3: Ptr::from(&DCI_P3.view), + rec2020: Ptr::from(&REC2020.view), + aces2065_1: Ptr::from(&ACES.view), + } +} diff --git a/src/textures/bilerp.rs b/src/textures/bilerp.rs new file mode 100644 index 0000000..2b3568a --- /dev/null +++ b/src/textures/bilerp.rs @@ -0,0 +1,57 @@ +use crate::core::texture::{CreateSpectrumTexture, FloatTextureTrait, SpectrumTextureTrait}; +use anyhow::Result; +use shared::core::texture::SpectrumType; +use shared::{ + textures::{FloatBilerpTexture, SpectrumBilerpTexture}, + utils::Transform, +}; + +use crate::{ + core::texture::FloatTexture, + utils::{FileLoc, TextureParameterDictionary}, +}; + +pub trait CreateFloatBilerpTexture { + fn create( + render_from_texture: Transform, + parameters: TextureParameterDictionary, + loc: FileLoc, + ) -> Result; +} + +impl CreateFloatBilerpTexture for FloatBilerpTexture { + fn create( + _render_from_texture: Transform, + _parameters: TextureParameterDictionary, + _loc: FileLoc, + ) -> Result { + todo!() + } +} + +impl FloatTextureTrait for FloatBilerpTexture { + fn evaluate(&self, _ctx: &shared::core::texture::TextureEvalContext) -> shared::Float { + todo!() + } +} + +impl CreateSpectrumTexture for SpectrumBilerpTexture { + fn create( + _render_from_texture: Transform, + _parameters: TextureParameterDictionary, + _spectrum_type: SpectrumType, + _loc: FileLoc, + ) -> Result { + todo!() + } +} + +impl SpectrumTextureTrait for SpectrumBilerpTexture { + fn evaluate( + &self, + _ctx: &shared::core::texture::TextureEvalContext, + _lambda: &shared::spectra::SampledWavelengths, + ) -> shared::spectra::SampledSpectrum { + todo!() + } +} diff --git a/src/textures/checkerboard.rs b/src/textures/checkerboard.rs new file mode 100644 index 0000000..1cb1ef6 --- /dev/null +++ b/src/textures/checkerboard.rs @@ -0,0 +1,59 @@ +use anyhow::Result; +use shared::{ + core::texture::SpectrumType, + textures::{FloatCheckerboardTexture, SpectrumCheckerboardTexture}, + utils::Transform, +}; + +use crate::{ + core::texture::{ + CreateSpectrumTexture, FloatTexture, FloatTextureTrait, SpectrumTexture, + SpectrumTextureTrait, + }, + utils::{FileLoc, TextureParameterDictionary}, +}; + +pub trait CreateFloatCheckerboardTexture { + fn create( + render_from_texture: Transform, + parameters: TextureParameterDictionary, + loc: FileLoc, + ) -> Result; +} + +impl CreateFloatCheckerboardTexture for FloatCheckerboardTexture { + fn create( + _render_from_texture: Transform, + _parameters: TextureParameterDictionary, + _loc: FileLoc, + ) -> Result { + todo!() + } +} + +impl FloatTextureTrait for FloatCheckerboardTexture { + fn evaluate(&self, _ctx: &shared::core::texture::TextureEvalContext) -> shared::Float { + todo!() + } +} + +impl CreateSpectrumTexture for SpectrumCheckerboardTexture { + fn create( + _render_from_texture: Transform, + _parameters: TextureParameterDictionary, + _spectrum_type: SpectrumType, + _loc: FileLoc, + ) -> Result { + todo!() + } +} + +impl SpectrumTextureTrait for SpectrumCheckerboardTexture { + fn evaluate( + &self, + _ctx: &shared::core::texture::TextureEvalContext, + _lambda: &shared::spectra::SampledWavelengths, + ) -> shared::spectra::SampledSpectrum { + todo!() + } +} diff --git a/src/textures/constant.rs b/src/textures/constant.rs new file mode 100644 index 0000000..cbecf78 --- /dev/null +++ b/src/textures/constant.rs @@ -0,0 +1,59 @@ +use anyhow::Result; +use shared::{ + core::texture::{SpectrumType, TextureEvalContext}, + textures::{FloatConstantTexture, SpectrumConstantTexture}, + utils::Transform, +}; + +use crate::{ + core::texture::{ + CreateSpectrumTexture, FloatTexture, FloatTextureTrait, SpectrumTextureTrait, + SpectrumTextureTrait, + }, + utils::{FileLoc, TextureParameterDictionary}, +}; + +pub trait CreateFloatConstantTexture { + fn create( + render_from_texture: Transform, + parameters: TextureParameterDictionary, + loc: FileLoc, + ) -> Result; +} + +impl CreateFloatConstantTexture for FloatConstantTexture { + fn create( + _render_from_texture: Transform, + _parameters: TextureParameterDictionary, + _loc: FileLoc, + ) -> Result { + todo!() + } +} + +impl FloatTextureTrait for FloatConstantTexture { + fn evaluate(&self, _ctx: &TextureEvalContext) -> shared::Float { + todo!() + } +} + +impl CreateSpectrumTexture for SpectrumConstantTexture { + fn create( + _render_from_texture: Transform, + _parameters: TextureParameterDictionary, + _spectrum_type: SpectrumType, + _loc: FileLoc, + ) -> Result { + todo!() + } +} + +impl SpectrumTextureTrait for SpectrumConstantTexture { + fn evaluate( + &self, + _ctx: &TextureEvalContext, + _lambda: &shared::spectra::SampledWavelengths, + ) -> shared::spectra::SampledSpectrum { + todo!() + } +} diff --git a/src/textures/dots.rs b/src/textures/dots.rs new file mode 100644 index 0000000..6742c07 --- /dev/null +++ b/src/textures/dots.rs @@ -0,0 +1,55 @@ +use anyhow::Result; +use shared::{ + textures::{FloatDotsTexture, SpectrumDotsTexture}, + utils::Transform, +}; + +use crate::{ + core::texture::{CreateSpectrumTexture, FloatTexture, FloatTextureTrait, SpectrumTextureTrait}, + utils::{FileLoc, TextureParameterDictionary}, +}; + +pub trait CreateFloatDotsTexture { + fn create( + render_from_texture: Transform, + parameters: TextureParameterDictionary, + loc: FileLoc, + ) -> Result; +} + +impl FloatTextureTrait for FloatDotsTexture { + fn evaluate(&self, ctx: &shared::core::texture::TextureEvalContext) -> shared::Float { + todo!() + } +} + +impl CreateFloatDotsTexture for FloatDotsTexture { + fn create( + _render_from_texture: Transform, + _parameters: TextureParameterDictionary, + _loc: FileLoc, + ) -> Result { + todo!() + } +} + +impl SpectrumTextureTrait for SpectrumScaledTexture { + fn evaluate( + &self, + _ctx: &shared::core::texture::TextureEvalContext, + _lambda: &shared::spectra::SampledWavelengths, + ) -> shared::spectra::SampledSpectrum { + todo!() + } +} + +impl CreateSpectrumTexture for SpectrumDotsTexture { + fn create( + _render_from_texture: Transform, + _parameters: TextureParameterDictionary, + _spectrum_type: SpectrumType, + _loc: FileLoc, + ) -> Result { + todo!() + } +} diff --git a/src/textures/fbm.rs b/src/textures/fbm.rs new file mode 100644 index 0000000..188d5e1 --- /dev/null +++ b/src/textures/fbm.rs @@ -0,0 +1,32 @@ +use anyhow::Result; +use shared::core::texture::TextureEvalContext; +use shared::{textures::FBmTexture, utils::Transform}; + +use crate::{ + core::texture::{FloatTexture, FloatTextureTrait}, + utils::{FileLoc, TextureParameterDictionary}, +}; + +pub trait CreateFBmTexture { + fn create( + render_from_texture: Transform, + parameters: TextureParameterDictionary, + loc: FileLoc, + ) -> Result; +} + +impl CreateFBmTexture for FBmTexture { + fn create( + _render_from_texture: Transform, + _parameters: TextureParameterDictionary, + _loc: FileLoc, + ) -> Result { + todo!() + } +} + +impl FloatTextureTrait for FBmTexture { + fn evaluate(&self, _ctx: &TextureEvalContext) -> shared::Float { + todo!() + } +} diff --git a/src/textures/image.rs b/src/textures/image.rs index 565485e..0c2c944 100644 --- a/src/textures/image.rs +++ b/src/textures/image.rs @@ -1,6 +1,10 @@ -use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait}; +use crate::core::texture::{ + CreateSpectrumTexture, FloatTexture, FloatTextureTrait, SpectrumTexture, SpectrumTextureTrait, +}; use crate::core::texture::{TexInfo, get_texture_cache}; +use crate::utils::TextureParameterDictionary; use crate::utils::mipmap::{MIPMap, MIPMapFilterOptions}; +use anyhow::Result; use shared::Float; use shared::core::color::ColorEncoding; use shared::core::color::RGB; @@ -147,6 +151,17 @@ impl SpectrumTextureTrait for SpectrumImageTexture { } } +impl CreateSpectrumTexture for SpectrumImageTexture { + fn create( + _render_from_texture: Transform, + _parameters: TextureParameterDictionary, + _spectrum_type: SpectrumType, + _loc: FileLoc, + ) -> Result { + todo!() + } +} + #[derive(Debug, Clone)] pub struct FloatImageTexture { pub base: ImageTextureBase, @@ -181,3 +196,21 @@ impl FloatTextureTrait for FloatImageTexture { todo!() } } + +pub trait CreateFloatImageTexture { + fn create( + render_from_texture: Transform, + parameters: TextureParameterDictionary, + loc: FileLoc, + ) -> Result; +} + +impl CreateFloatImageTexture for FloatImageTexture { + fn create( + _render_from_texture: Transform, + _parameters: TextureParameterDictionary, + _loc: FileLoc, + ) -> Result { + todo!() + } +} diff --git a/src/textures/marble.rs b/src/textures/marble.rs new file mode 100644 index 0000000..16c3820 --- /dev/null +++ b/src/textures/marble.rs @@ -0,0 +1,23 @@ +use crate::core::texture::{CreateSpectrumTexture, SpectrumTexture, SpectrumTextureTrait}; +use shared::textures::MarbleTexture; + +impl SpectrumTextureTrait for MarbleTexture { + fn evaluate( + &self, + _ctx: &shared::core::texture::TextureEvalContext, + _lambda: &shared::spectra::SampledWavelengths, + ) -> shared::spectra::SampledSpectrum { + todo!() + } +} + +impl CreateSpectrumTexture for MarbleTexture { + fn create( + render_from_texture: shared::utils::Transform, + parameters: crate::utils::TextureParameterDictionary, + spectrum_type: SpectrumType, + loc: crate::utils::FileLoc, + ) -> anyhow::Result { + todo!() + } +} diff --git a/src/textures/mix.rs b/src/textures/mix.rs index c10198f..3b73903 100644 --- a/src/textures/mix.rs +++ b/src/textures/mix.rs @@ -1,7 +1,8 @@ use crate::core::texture::{ - FloatTexture, FloatTextureTrait, SpectrumTexture, SpectrumTextureTrait, + CreateSpectrumTexture, FloatTexture, FloatTextureTrait, SpectrumTexture, SpectrumTextureTrait, }; use crate::utils::{Arena, FileLoc, TextureParameterDictionary}; +use anyhow::Result; use shared::Float; use shared::core::geometry::{Vector3f, VectorLike}; use shared::core::texture::TextureEvalContext; @@ -30,12 +31,12 @@ impl FloatMixTexture { params: &TextureParameterDictionary, _loc: &FileLoc, _arena: &mut Arena, - ) -> FloatTexture { + ) -> Result { 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)); - FloatTexture::Mix(Self::new(tex1, tex2, amount)) + Ok(FloatTexture::Mix(Self::new(tex1, tex2, amount))) } } @@ -71,13 +72,13 @@ impl FloatDirectionMixTexture { params: &TextureParameterDictionary, _loc: &FileLoc, _arena: &mut Arena, - ) -> FloatTexture { + ) -> Result { 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)) - FloatTexture::DirectionMix(Self::new(tex1, tex2, dir)) + Ok(FloatTexture::DirectionMix(Self::new(tex1, tex2, dir))) } } @@ -94,6 +95,17 @@ pub struct SpectrumMixTexture { pub amount: Arc, } +impl CreateSpectrumTexture for SpectrumMixTexture { + fn create( + render_from_texture: Transform, + parameters: TextureParameterDictionary, + spectrum_type: SpectrumType, + loc: FileLoc, + ) -> Result { + todo!() + } +} + impl SpectrumTextureTrait for SpectrumMixTexture { fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { todo!() @@ -107,6 +119,17 @@ pub struct SpectrumDirectionMixTexture { pub dir: Vector3f, } +impl CreateSpectrumTexture for SpectrumDirectionMixTexture { + fn create( + render_from_texture: Transform, + parameters: TextureParameterDictionary, + spectrum_type: SpectrumType, + loc: FileLoc, + ) -> Result { + todo!() + } +} + impl SpectrumTextureTrait for SpectrumDirectionMixTexture { fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { todo!() diff --git a/src/textures/mod.rs b/src/textures/mod.rs index 16afa38..4560fc8 100644 --- a/src/textures/mod.rs +++ b/src/textures/mod.rs @@ -1,9 +1,25 @@ -pub mod image; -pub mod mix; -pub mod ptex; -pub mod scaled; +mod bilerp; +mod checkerboard; +mod constant; +mod dots; +mod fbm; +mod image; +mod marble; +mod mix; +mod ptex; +mod scaled; +mod windy; +mod wrinkled; +pub use bilerp::*; +pub use checkerboard::*; +pub use constant::*; +pub use dots::*; +pub use fbm::*; pub use image::*; +pub use marble::*; pub use mix::*; pub use ptex::*; pub use scaled::*; +pub use windy::*; +pub use wrinkled::*; diff --git a/src/textures/scaled.rs b/src/textures/scaled.rs index 0c5bffd..43d5bcd 100644 --- a/src/textures/scaled.rs +++ b/src/textures/scaled.rs @@ -1,6 +1,7 @@ -use crate::core::texture::{FloatTexture, SpectrumTexture}; +use crate::core::texture::{CreateSpectrumTexture, FloatTexture, SpectrumTexture}; use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait}; use crate::utils::{Arena, FileLoc, TextureParameterDictionary}; +use anyhow::Result; use shared::Float; use shared::core::texture::TextureEvalContext; use shared::spectra::{SampledSpectrum, SampledWavelengths}; @@ -23,7 +24,7 @@ impl FloatScaledTexture { params: &TextureParameterDictionary, _loc: &FileLoc, _arena: &mut Arena, - ) -> FloatTexture { + ) -> Result { let mut tex = params.get_float_texture("tex", 1.); let mut scale = params.get_float_texture("scale", 1.); @@ -31,11 +32,11 @@ impl FloatScaledTexture { if let FloatTexture::Constant(c_tex) = &*scale { let cs = c_tex.value; if cs == 1.0 { - return (*tex).clone(); + return Ok((*tex).clone()); } else if let FloatTexture::Image(img_tex) = &*tex { let mut image_copy = img_tex.clone(); image_copy.base.multiply_scale(cs); - return FloatTexture::Image(image_copy).into(); + return Ok(FloatTexture::Image(image_copy)); } } @@ -43,7 +44,10 @@ impl FloatScaledTexture { } std::mem::swap(&mut tex, &mut scale); // arena.alloc(FloatScaledTexture::new(tex, scale)); - FloatTexture::Scaled(FloatScaledTexture::new(tex.clone(), scale.clone())) + Ok(FloatTexture::Scaled(FloatScaledTexture::new( + tex.clone(), + scale.clone(), + ))) } } @@ -63,6 +67,17 @@ pub struct SpectrumScaledTexture { pub scale: Arc, } +impl CreateSpectrumTexture for SpectrumScaledTexture { + fn create( + render_from_texture: Transform, + parameters: TextureParameterDictionary, + spectrum_type: SpectrumType, + loc: FileLoc, + ) -> Result { + todo!() + } +} + impl SpectrumTextureTrait for SpectrumScaledTexture { fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum { let sc = self.scale.evaluate(ctx); diff --git a/src/textures/windy.rs b/src/textures/windy.rs new file mode 100644 index 0000000..17e357b --- /dev/null +++ b/src/textures/windy.rs @@ -0,0 +1,31 @@ +use anyhow::Result; +use shared::{textures::WindyTexture, utils::Transform}; + +use crate::{ + core::texture::{FloatTexture, FloatTextureTrait}, + utils::{FileLoc, TextureParameterDictionary}, +}; + +pub trait CreateWindyTexture { + fn create( + render_from_texture: Transform, + parameters: TextureParameterDictionary, + loc: FileLoc, + ) -> Result; +} + +impl CreateWindyTexture for WindyTexture { + fn create( + _render_from_texture: Transform, + _parameters: TextureParameterDictionary, + _loc: FileLoc, + ) -> Result { + todo!() + } +} + +impl FloatTextureTrait for WindyTexture { + fn evaluate(&self, _ctx: &shared::core::texture::TextureEvalContext) -> shared::Float { + todo!() + } +} diff --git a/src/textures/wrinkled.rs b/src/textures/wrinkled.rs new file mode 100644 index 0000000..1e39cfa --- /dev/null +++ b/src/textures/wrinkled.rs @@ -0,0 +1,31 @@ +use anyhow::Result; +use shared::{textures::WrinkledTexture, utils::Transform}; + +use crate::{ + core::texture::{FloatTexture, FloatTextureTrait}, + utils::{FileLoc, TextureParameterDictionary}, +}; + +pub trait CreateWrinkledTexture { + fn create( + render_from_texture: Transform, + parameters: TextureParameterDictionary, + loc: FileLoc, + ) -> Result; +} + +impl CreateWrinkledTexture for WrinkledTexture { + fn create( + _render_from_texture: Transform, + _parameters: TextureParameterDictionary, + _loc: FileLoc, + ) -> Result { + todo!() + } +} + +impl FloatTextureTrait for WrinkledTexture { + fn evaluate(&self, _ctx: &shared::core::texture::TextureEvalContext) -> shared::Float { + todo!() + } +} diff --git a/src/utils/arena.rs b/src/utils/arena.rs index 7abe412..21da746 100644 --- a/src/utils/arena.rs +++ b/src/utils/arena.rs @@ -1,18 +1,21 @@ use crate::core::image::Image; use crate::core::texture::{FloatTexture, SpectrumTexture}; use crate::shapes::{BilinearPatchMesh, TriangleMesh}; +use crate::spectra::DenselySampledSpectrumBuffer; use crate::utils::mipmap::MIPMap; -use crate::utils::sampling::PiecewiseConstant2D; +use crate::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D}; 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::{DeviceStandardColorSpaces, RGBColorSpace}; +use shared::spectra::{DenselySampledSpectrum, DeviceStandardColorSpaces, RGBColorSpace}; use shared::textures::*; use shared::utils::Ptr; use shared::utils::mesh::{DeviceBilinearPatchMesh, DeviceTriangleMesh}; -use shared::utils::sampling::{DevicePiecewiseConstant1D, DevicePiecewiseConstant2D}; +use shared::utils::sampling::{ + DevicePiecewiseConstant1D, DevicePiecewiseConstant2D, DeviceWindowedPiecewiseConstant2D, +}; use std::alloc::Layout; use std::collections::HashMap; use std::slice::from_raw_parts; @@ -166,6 +169,13 @@ impl Upload for Spectrum { } } +impl Upload for DenselySampledSpectrumBuffer { + type Target = DenselySampledSpectrum; + fn upload(&self, arena: &mut Arena) -> Ptr { + arena.alloc(self.device) + } +} + impl Upload for SpectrumTexture { type Target = GPUSpectrumTexture; fn upload(&self, arena: &mut Arena) -> Ptr { @@ -291,10 +301,6 @@ impl Upload for FloatTexture { GPUFloatTexture::Image(gpu_image_tex) } - // FloatTexture::Ptex(tex) => { - // todo!("Implement Ptex buffer upload") - // } - FloatTexture::Bilerp(tex) => GPUFloatTexture::Bilerp(tex.clone()), }; @@ -387,6 +393,17 @@ impl Upload for PiecewiseConstant2D { } } +impl Upload for WindowedPiecewiseConstant2D { + type Target = DeviceWindowedPiecewiseConstant2D; + fn upload(&self, arena: &mut Arena) -> Ptr { + let specific = DeviceWindowedPiecewiseConstant2D { + sat: self.sat, + func: self.func, + }; + arena.alloc(specific) + } +} + impl Upload for TriangleMesh { type Target = DeviceTriangleMesh; diff --git a/src/utils/containers.rs b/src/utils/containers.rs index 8211db2..9e2124b 100644 --- a/src/utils/containers.rs +++ b/src/utils/containers.rs @@ -35,35 +35,36 @@ where #[derive(Debug, Clone)] pub struct Array2D { - pub view: DeviceArray2D, - _storage: Vec, + pub device: DeviceArray2D, + values: Vec, } impl Deref for Array2D { type Target = DeviceArray2D; fn deref(&self) -> &Self::Target { - &self.view + &self.device } } impl DerefMut for Array2D { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.view + &mut self.device } } impl Array2D { - fn from_vec(extent: Bounds2i, mut storage: Vec) -> Self { + pub fn as_slice(&self) -> &[T] { + &self.values + } + + fn from_vec(extent: Bounds2i, mut values: Vec) -> Self { let width = extent.p_max.x() - extent.p_min.x(); - let view = DeviceArray2D { + let device = DeviceArray2D { extent, values: storage.as_mut_ptr(), stride: width, }; - Self { - view, - _storage: storage, - } + Self { device, values } } } diff --git a/src/utils/parameters.rs b/src/utils/parameters.rs index 53f95a7..2e279e0 100644 --- a/src/utils/parameters.rs +++ b/src/utils/parameters.rs @@ -1,4 +1,4 @@ -use crate::core::spectrum::SPECTRUM_CACHE; +use crate::core::spectrum::{SPECTRUM_CACHE, get_spectrum_cache}; use crate::core::texture::{FloatTexture, SpectrumTexture}; use crate::spectra::data::get_named_spectrum; use crate::spectra::piecewise::PiecewiseLinearSpectrumBuffer; @@ -683,7 +683,7 @@ impl ParameterDictionary { fn read_spectrum_from_file(filename: &str) -> Result { let fn_key = filename.to_string(); { - let cache = SPECTRUM_CACHE.lock(); + let cache = SPECTRUM_FILE_CACHE.lock(); if let Some(s) = cache.get(&fn_key) { return Ok(s.clone()); } @@ -695,7 +695,7 @@ fn read_spectrum_from_file(filename: &str) -> Result { let spectrum = Spectrum::Piecewise(*pls); { - let mut cache = SPECTRUM_CACHE.lock(); + let mut cache = SPECTRUM_FILE_CACHE.lock(); cache.insert(fn_key, spectrum.clone()); }