diff --git a/shared/src/core/bssrdf.rs b/shared/src/core/bssrdf.rs index bccbed6..f1766c5 100644 --- a/shared/src/core/bssrdf.rs +++ b/shared/src/core/bssrdf.rs @@ -6,8 +6,8 @@ use crate::core::shape::Shape; use crate::spectra::{SampledSpectrum, N_SPECTRUM_SAMPLES}; use crate::utils::math::{catmull_rom_weights, square}; use crate::utils::sampling::sample_catmull_rom_2d; -use crate::utils::Ptr; -use crate::{gvec_with_capacity, Float, GVec, PI}; +use crate::core::{LightIdx, MaterialIdx}; +use crate::{gvec_with_capacity, Float, GVec, PI, Ptr}; use enum_dispatch::enum_dispatch; use num_traits::Float as NumFloat; @@ -78,8 +78,8 @@ impl From<&SubsurfaceInteraction> for SurfaceInteraction { dndv: Normal3f::zero(), }, face_index: 0, - area_light: Ptr::null(), - material: Ptr::null(), + area_light: LightIdx::default(), + material: MaterialIdx::default(), dpdx: Vector3f::zero(), dpdy: Vector3f::zero(), dudx: 0., diff --git a/shared/src/core/handle.rs b/shared/src/core/handle.rs new file mode 100644 index 0000000..199cabc --- /dev/null +++ b/shared/src/core/handle.rs @@ -0,0 +1,34 @@ +use crate::core::light::Light; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct LightIdx(pub u32); + +impl LightIdx { + pub const NONE: Self = LightIdx(u32::MAX); + pub fn is_none(self) -> bool { self.0 == u32::MAX } +} + +impl Default for LightIdx { + fn default() -> Self { Self::NONE } +} + +impl LightIdx { + #[inline] + pub fn get(self, lights: &[Light]) -> &Light { + &lights[self.0 as usize] + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct MaterialIdx(pub u32); + +impl MaterialIdx { + pub const NONE: Self = MaterialIdx(u32::MAX); + pub fn is_none(self) -> bool { self.0 == u32::MAX } +} + +impl Default for MaterialIdx { + fn default() -> Self { Self::NONE } +} diff --git a/shared/src/core/interaction.rs b/shared/src/core/interaction.rs index 031ea6b..5aca6d4 100644 --- a/shared/src/core/interaction.rs +++ b/shared/src/core/interaction.rs @@ -1,4 +1,3 @@ -use crate::Float; use crate::bxdfs::DiffuseBxDF; use crate::core::bsdf::BSDF; use crate::core::bssrdf::BSSRDF; @@ -10,15 +9,16 @@ use crate::core::geometry::{ use crate::core::image::Image; use crate::core::light::{Light, LightTrait}; use crate::core::material::{ - Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map, + bump_map, normal_map, Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, }; use crate::core::medium::{Medium, MediumInterface, PhaseFunction}; use crate::core::sampler::{Sampler, SamplerTrait}; use crate::core::shape::Shape; use crate::core::texture::{FloatTexture, UniversalTextureEvaluator}; +use crate::core::{LightIdx, MaterialIdx}; use crate::spectra::{SampledSpectrum, SampledWavelengths}; -use crate::utils::Ptr; use crate::utils::math::{clamp, difference_of_products, square}; +use crate::{GVec, Ptr, Float}; use enum_dispatch::enum_dispatch; #[repr(C)] @@ -217,8 +217,8 @@ pub struct ShadingGeom { #[repr(C)] #[derive(Debug, Default, Clone, Copy)] pub struct SurfaceInteraction { - pub area_light: Ptr, - pub material: Ptr, + pub area_light: LightIdx, + pub material: MaterialIdx, pub shape: Ptr, pub common: InteractionBase, pub shading: ShadingGeom, @@ -239,13 +239,17 @@ unsafe impl Send for SurfaceInteraction {} unsafe impl Sync for SurfaceInteraction {} impl SurfaceInteraction { - pub fn le(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { - if !self.area_light.is_null() { - self.area_light - .l(self.p(), self.n(), self.common.uv, w, lambda) - } else { - SampledSpectrum::new(0.) + pub fn le( + &self, + w: Vector3f, + lambda: &SampledWavelengths, + lights: &GVec, + ) -> SampledSpectrum { + if self.area_light.is_none() { + return SampledSpectrum::new(0.); } + let light = self.area_light.get(lights); + light.l(self.p(), self.n(), self.common.uv, w, lambda) } pub fn compute_differentials(&mut self, r: &Ray, camera: &Camera, samples_per_pixel: i32) { @@ -519,9 +523,9 @@ impl SurfaceInteraction { dndu, dndv, }, - material: Ptr::null(), + material: MaterialIdx::default(), face_index: 0, - area_light: Ptr::null(), + area_light: LightIdx::default(), dpdx: Vector3f::zero(), dpdy: Vector3f::zero(), dudx: 0.0, @@ -591,8 +595,8 @@ impl SurfaceInteraction { #[cfg(not(target_os = "cuda"))] pub fn set_intersection_properties( &mut self, - mtl: Ptr, - area: Ptr, + mtl: MaterialIdx, + area: LightIdx, ray_medium: Ptr, prim_medium_interface: MediumInterface, ) { diff --git a/shared/src/core/mod.rs b/shared/src/core/mod.rs index c9c0747..8f192a3 100644 --- a/shared/src/core/mod.rs +++ b/shared/src/core/mod.rs @@ -7,6 +7,7 @@ pub mod color; pub mod film; pub mod filter; pub mod geometry; +pub mod handle; pub mod image; pub mod interaction; pub mod light; @@ -19,3 +20,5 @@ pub mod scattering; pub mod shape; pub mod spectrum; pub mod texture; + +pub use handle::{LightIdx, MaterialIdx}; diff --git a/shared/src/core/primitive.rs b/shared/src/core/primitive.rs index a23516a..7c4342d 100644 --- a/shared/src/core/primitive.rs +++ b/shared/src/core/primitive.rs @@ -4,12 +4,12 @@ use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction use crate::core::light::Light; use crate::core::material::Material; use crate::core::medium::{Medium, MediumInterface}; -use crate::core::pbrt::Float; use crate::core::shape::{Shape, ShapeIntersection, ShapeTrait}; use crate::core::texture::{FloatTexture, TextureEvalContext}; +use crate::core::{LightIdx, MaterialIdx}; use crate::utils::hash::hash_float; use crate::utils::transform::{AnimatedTransform, Transform}; -use crate::utils::Ptr; +use crate::{Float, Ptr}; use alloc::boxed::Box; use alloc::sync::Arc; @@ -26,8 +26,8 @@ pub trait PrimitiveTrait: Send + Sync { #[derive(Debug, Clone, Copy)] pub struct GeometricPrimitive { pub shape: Ptr, - pub material: Ptr, - pub area_light: Ptr, + pub material: MaterialIdx, + pub area_light: LightIdx, pub medium_interface: MediumInterface, pub alpha: Ptr, } @@ -91,7 +91,7 @@ impl PrimitiveTrait for GeometricPrimitive { #[derive(Debug, Copy, Clone)] pub struct SimplePrimitive { pub shape: Ptr, - pub material: Ptr, + pub material: MaterialIdx, } impl PrimitiveTrait for SimplePrimitive { @@ -103,7 +103,7 @@ impl PrimitiveTrait for SimplePrimitive { let mut si = self.shape.intersect(r, t_max)?; si.set_intersection_properties( self.material, - Ptr::null(), + LightIdx::default(), MediumInterface::default(), r.medium, ); diff --git a/shared/src/core/shape.rs b/shared/src/core/shape.rs index bb69f36..b375002 100644 --- a/shared/src/core/shape.rs +++ b/shared/src/core/shape.rs @@ -5,6 +5,7 @@ use crate::core::geometry::{ use crate::core::interaction::{ Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction, }; +use crate::core::{MaterialIdx, LightIdx}; use crate::core::light::Light; use crate::core::material::Material; use crate::core::medium::{Medium, MediumInterface}; @@ -37,8 +38,8 @@ impl ShapeIntersection { pub fn set_intersection_properties( &mut self, - mtl: Ptr, - area: Ptr, + mtl: MaterialIdx, + area: LightIdx, prim_medium_interface: MediumInterface, ray_medium: Ptr, ) { diff --git a/shared/src/lights/sampler.rs b/shared/src/lights/sampler.rs index 4c21c5c..16af7b4 100644 --- a/shared/src/lights/sampler.rs +++ b/shared/src/lights/sampler.rs @@ -1,8 +1,7 @@ use crate::core::geometry::primitives::OctahedralVector; -use crate::core::geometry::{Bounds3f, Normal3f, Point3f, Vector3f, VectorLike}; -use crate::core::geometry::{DirectionCone, Normal}; -use crate::core::light::Light; -use crate::core::light::{LightBounds, LightSampleContext}; +use crate::core::geometry::{Bounds3f, DirectionCone, Normal3f, Point3f, Vector3f, VectorLike}; +use crate::core::light::{Light, LightBounds, LightSampleContext}; +use crate::core::LightIdx; use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::utils::math::{clamp, lerp, sample_discrete}; use crate::utils::math::{safe_sqrt, square}; @@ -167,25 +166,25 @@ impl CompactLightBounds { #[derive(Debug, Clone)] pub struct SampledLight { - pub light: Ptr, + pub light: LightIdx, pub p: Float, } -impl SampledLight { - pub fn new(light: Light, p: Float) -> Self { - Self { - light: Ptr::from(&light), - p, - } - } -} - +// impl SampledLight { +// pub fn new(light: Light, p: Float) -> Self { +// Self { +// light: Ptr::from(&light), +// p, +// } +// } +// } +// #[enum_dispatch] pub trait LightSamplerTrait { fn sample_with_context(&self, ctx: &LightSampleContext, u: Float) -> Option; - fn pmf_with_context(&self, ctx: &LightSampleContext, light: &Light) -> Float; + fn pmf_with_context(&self, ctx: &LightSampleContext, idx: LightIdx) -> Float; fn sample(&self, u: Float) -> Option; - fn pmf(&self, light: &Light) -> Float; + fn pmf(&self, idx: LightIdx) -> Float; } #[derive(Clone, Debug)] @@ -198,18 +197,12 @@ pub enum LightSampler { #[derive(Clone, Debug)] pub struct UniformLightSampler { - lights: Ptr, lights_len: u32, } impl UniformLightSampler { - pub fn new(lights: Ptr, lights_len: u32) -> Self { - Self { lights, lights_len } - } - - #[inline(always)] - fn light(&self, idx: usize) -> Light { - unsafe { *self.lights.add(idx) } + pub fn new(lights_len: u32) -> Self { + Self { lights_len } } } @@ -217,39 +210,33 @@ impl LightSamplerTrait for UniformLightSampler { fn sample_with_context(&self, _ctx: &LightSampleContext, u: Float) -> Option { self.sample(u) } - fn pmf_with_context(&self, _ctx: &LightSampleContext, light: &Light) -> Float { - self.pmf(light) - } + fn sample(&self, u: Float) -> Option { if self.lights_len == 0 { return None; } - - let light_index = (u as u32 * self.lights_len).min(self.lights_len - 1) as usize; + let light_index = ((u * self.lights_len as Float) as u32).min(self.lights_len - 1); Some(SampledLight { - light: Ptr::from(&self.light(light_index)), - p: 1. / self.lights_len as Float, + light: LightIdx(light_index), + p: 1.0 / self.lights_len as Float, }) } - fn pmf(&self, _light: &Light) -> Float { - if self.lights_len == 0 { - return 0.; - } - 1. / self.lights_len as Float - } -} -#[repr(C)] -#[derive(Clone, Copy, Debug)] -pub struct Alias { - pub q: Float, - pub alias: u32, + fn pmf_with_context(&self, _ctx: &LightSampleContext, _idx: LightIdx) -> Float { + self.pmf(_idx) + } + + fn pmf(&self, _idx: LightIdx) -> Float { + if self.lights_len == 0 { + return 0.0; + } + 1.0 / self.lights_len as Float + } } #[repr(C)] #[derive(Clone, Debug, Copy)] pub struct PowerLightSampler { - pub lights: Ptr, pub lights_len: u32, pub alias_table: Ptr, } @@ -262,32 +249,23 @@ impl LightSamplerTrait for PowerLightSampler { self.sample(u) } - fn pmf_with_context(&self, _ctx: &LightSampleContext, light: &Light) -> Float { - self.pmf(light) + fn pmf_with_context(&self, _ctx: &LightSampleContext, idx: LightIdx) -> Float { + self.pmf(idx) } fn sample(&self, u: Float) -> Option { if self.alias_table.size() == 0 { return None; } - let (light_index, pmf, _) = self.alias_table.sample(u); - - let light_ref = unsafe { self.lights.add(light_index as usize) }; Some(SampledLight { - light: light_ref, + light: LightIdx(light_index), p: pmf, }) } - fn pmf(&self, light: &Light) -> Float { - let target = light as *const Light; - for i in 0..self.lights_len as usize { - if unsafe { self.lights.add(i) }.as_raw() == target { - return self.alias_table.pmf(i as u32); - } - } - 0.0 + fn pmf(&self, idx: LightIdx) -> Float { + self.alias_table.pmf(idx.0) } } @@ -426,15 +404,17 @@ impl LightSamplerTrait for BVHLightSampler { fn sample_with_context(&self, ctx: &LightSampleContext, mut u: Float) -> Option { let empty_nodes = if self.nodes_len == 0 { 0. } else { 1. }; let inf_size = self.infinite_lights_len as Float; - let light_size = self.lights_len as Float; let p_inf = inf_size / (inf_size + empty_nodes); if u < p_inf { u /= p_inf; - let ind = (u * light_size).min(light_size - 1.) as usize; + // sample uniformly from infinite lights; their global index equals their + // position in the infinite_lights array (infinite lights are at 0..n_inf + // in the scene lights array by construction) + let ind = (u * inf_size).min(inf_size - 1.) as u32; let pmf = p_inf / inf_size; - return Some(SampledLight::new(self.infinite_light(ind), pmf)); + return Some(SampledLight { light: LightIdx(ind), p: pmf }); } if self.nodes_len == 0 { @@ -469,28 +449,32 @@ impl LightSamplerTrait for BVHLightSampler { node_ind = if child == 0 { child0_idx } else { child1_idx }; } else { if node_ind > 0 || node.light_bounds.importance(p, n, &self.all_light_bounds) > 0. { - let light_idx = node.child_or_light_index() as usize; - return Some(SampledLight::new(self.light(light_idx), pmf)); + // child_or_light_index() is the global index into the scene lights array + return Some(SampledLight { + light: LightIdx(node.child_or_light_index()), + p: pmf, + }); } return None; } } } - fn pmf_with_context(&self, ctx: &LightSampleContext, light: &Light) -> Float { + fn pmf_with_context(&self, ctx: &LightSampleContext, idx: LightIdx) -> Float { let empty_nodes = if self.nodes_len == 0 { 0. } else { 1. }; let n_infinite = self.infinite_lights_len as Float; - if self - .light_index_in(self.infinite_lights, self.infinite_lights_len, light) - .is_some() - { + + // Infinite lights occupy indices 0..infinite_lights_len in the global array + if idx.0 < self.infinite_lights_len { return 1.0 / (n_infinite + empty_nodes); } - let Some(light_index) = self.light_index_in(self.lights, self.lights_len, light) else { + let light_index = idx.0 as usize; + if light_index >= self.lights_len as usize { return 0.0; - }; + } + // bit_trail[light_index] encodes the path from root to the leaf for this light let mut bit_trail = self.bit_trail(light_index); let p_inf = n_infinite / (n_infinite + empty_nodes); let mut pmf = 1.0 - p_inf; @@ -516,17 +500,12 @@ impl LightSamplerTrait for BVHLightSampler { } let which_child = (bit_trail & 1) as usize; - - // Update probability: prob of picking the correct child pmf *= ci[which_child] / sum_importance; - - // Advance node_ind = if which_child == 1 { node.child_or_light_index() as usize } else { node_ind + 1 }; - bit_trail >>= 1; } } @@ -535,20 +514,17 @@ impl LightSamplerTrait for BVHLightSampler { if self.lights_len == 0 { return None; } - - let light_ind = (u * self.lights_len as Float).min(self.lights_len as Float - 1.) as usize; - - Some(SampledLight::new( - self.light(light_ind), - 1. / self.lights_len as Float, - )) + let light_ind = (u * self.lights_len as Float).min(self.lights_len as Float - 1.) as u32; + Some(SampledLight { + light: LightIdx(light_ind), + p: 1. / self.lights_len as Float, + }) } - fn pmf(&self, _light: &Light) -> Float { + fn pmf(&self, _idx: LightIdx) -> Float { if self.lights_len == 0 { return 0.; } - 1. / self.lights_len as Float } } diff --git a/shared/src/wavefront/integrator.rs b/shared/src/wavefront/integrator.rs index e272810..252b4fc 100644 --- a/shared/src/wavefront/integrator.rs +++ b/shared/src/wavefront/integrator.rs @@ -3,6 +3,8 @@ use crate::core::film::Film; use crate::core::filter::Filter; use crate::core::light::Light; use crate::core::sampler::Sampler; +use crate::core::material::Material; +use crate::core::LightIdx; use crate::lights::sampler::LightSampler; use crate::wavefront::aggregate::WavefrontAggregate; use crate::wavefront::workitems::*; @@ -15,7 +17,9 @@ pub struct WavefrontPathIntegrator { pub max_depth: u32, pub samples_per_pixel: u32, pub regularize: bool, - pub infinite_lights: GVec>, + pub lights: GVec, + pub materials: GVec, + pub infinite_lights: GVec, pub max_queue_size: u32, pub scanlines_per_pass: u32, pub light_sampler: LightSampler, @@ -33,4 +37,3 @@ pub struct WavefrontPathIntegrator { pub trait WavefrontRenderer { fn render(&mut self); } - diff --git a/shared/src/wavefront/workitems.rs b/shared/src/wavefront/workitems.rs index 3498be4..0e141bb 100644 --- a/shared/src/wavefront/workitems.rs +++ b/shared/src/wavefront/workitems.rs @@ -3,8 +3,8 @@ use crate::core::film::VisibleSurface; use crate::core::geometry::{ Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray, RayDifferential, Vector3f, }; -use crate::core::light::Light; -use crate::core::light::LightSampleContext; +use crate::core::light::{Light, LightSampleContext}; +use crate::core::{MaterialIdx, LightIdx}; use crate::core::material::Material; use crate::core::medium::{Medium, MediumInterface}; use crate::spectra::{SampledSpectrum, SampledWavelengths}; @@ -207,7 +207,7 @@ impl SoA for EscapedRayWorkItemSoA { #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct HitAreaLightWorkItem { - pub area_light: Ptr, + pub area_light: LightIdx, pub p: Point3f, pub n: Normal3f, pub uv: Point2f, @@ -225,7 +225,7 @@ pub struct HitAreaLightWorkItem { #[repr(C)] #[derive(Clone, Copy)] pub struct HitAreaLightWorkItemSoA { - pub area_light: SoABuffer>, + pub area_light: SoABuffer, pub p: SoABuffer, pub n: SoABuffer, pub uv: SoABuffer, @@ -308,8 +308,8 @@ pub struct MaterialEvalWorkItem { pub wo: Vector3f, pub time: Float, pub face_index: i32, - pub material: Ptr, - pub area_light: Ptr, + pub material: MaterialIdx, + pub area_light: LightIdx, pub medium_interface: MediumInterface, pub pixel_index: u32, pub lambda: SampledWavelengths, @@ -336,8 +336,8 @@ pub struct MaterialEvalWorkItemSoA { pub wo: SoABuffer, pub time: SoABuffer, pub face_index: SoABuffer, - pub material: SoABuffer>, - pub area_light: SoABuffer>, + pub material: SoABuffer, + pub area_light: SoABuffer, pub medium_interface: SoABuffer, pub pixel_index: SoABuffer, pub lambda: SoABuffer, diff --git a/src/core/interaction.rs b/src/core/interaction.rs index 0f8757e..d0621ef 100644 --- a/src/core/interaction.rs +++ b/src/core/interaction.rs @@ -19,6 +19,7 @@ pub trait InteractionGetter { lambda: &SampledWavelengths, camera: &Camera, sampler: &mut Sampler, + materials: &[Material], ) -> Option; fn get_bssrdf( @@ -26,6 +27,7 @@ pub trait InteractionGetter { _ray: &Ray, lambda: &SampledWavelengths, _camera: &Camera, + materials: &[Material], ) -> Option; } @@ -36,18 +38,20 @@ impl InteractionGetter for SurfaceInteraction { lambda: &SampledWavelengths, camera: &Camera, sampler: &mut Sampler, + materials: &[Material], ) -> Option { self.compute_differentials(r, camera, sampler.samples_per_pixel()); + if self.material.is_none() { + return None; + } + let mut active_mat: &Material = &materials[self.material.0 as usize]; let material = { - let Some(mut active_mat) = self.material.get() else { - return None; - }; let tex_eval = UniversalTextureEvaluator; while let Material::Mix(mix) = active_mat { let ctx = MaterialEvalContext::from(&*self); active_mat = mix.choose_material(&tex_eval, &ctx)?; } - *active_mat + active_mat }; let ctx = MaterialEvalContext::from(&*self); let tex_eval = UniversalTextureEvaluator; @@ -73,10 +77,12 @@ impl InteractionGetter for SurfaceInteraction { _ray: &Ray, lambda: &SampledWavelengths, _camera: &Camera, + materials: &[Material], ) -> Option { - let Some(mut active_mat) = self.material.get() else { + if self.material.is_none() { return None; - }; + } + let mut active_mat: &Material = &materials[self.material.0 as usize]; let tex_eval = UniversalTextureEvaluator; while let Material::Mix(mix) = active_mat { let ctx = MaterialEvalContext::from(self); @@ -95,6 +101,7 @@ impl InteractionGetter for MediumInteraction { _lambda: &SampledWavelengths, _camera: &Camera, _sampler: &mut Sampler, + _materials: &[Material], ) -> Option { None } @@ -104,6 +111,7 @@ impl InteractionGetter for MediumInteraction { _ray: &Ray, _lambda: &SampledWavelengths, _camera: &Camera, + _materials: &[Material], ) -> Option { None } @@ -116,6 +124,7 @@ impl InteractionGetter for SimpleInteraction { _lambda: &SampledWavelengths, _camera: &Camera, _sampler: &mut Sampler, + _materials: &[Material], ) -> Option { None } @@ -125,6 +134,7 @@ impl InteractionGetter for SimpleInteraction { _ray: &Ray, _lambda: &SampledWavelengths, _camera: &Camera, + _materials: &[Material], ) -> Option { None } diff --git a/src/core/primitive.rs b/src/core/primitive.rs index 4faa16d..6ac3753 100644 --- a/src/core/primitive.rs +++ b/src/core/primitive.rs @@ -1,4 +1,5 @@ use shared::core::{ + LightIdx, MaterialIdx, light::Light, material::Material, medium::MediumInterface, @@ -6,11 +7,10 @@ use shared::core::{ shape::Shape, texture::FloatTexture, }; - -use shared::utils::Ptr; +use shared::Ptr; pub trait CreateSimplePrimitive { - fn new(shape: Ptr, material: Ptr) -> SimplePrimitive { + fn new(shape: Ptr, material: MaterialIdx) -> SimplePrimitive { SimplePrimitive { shape, material } } } @@ -20,8 +20,8 @@ impl CreateSimplePrimitive for SimplePrimitive {} pub trait CreateGeometricPrimitive { fn new( shape: Ptr, - material: Ptr, - area_light: Ptr, + material: MaterialIdx, + area_light: LightIdx, medium_interface: MediumInterface, alpha: Ptr, ) -> GeometricPrimitive { diff --git a/src/core/render.rs b/src/core/render.rs index eb6363a..2fc78ee 100644 --- a/src/core/render.rs +++ b/src/core/render.rs @@ -17,7 +17,7 @@ pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { let media = scene.create_media(); let textures = scene.create_textures(arena); let (named_materials, materials) = scene.create_materials(&textures, arena)?; - let lights = scene.create_lights(&textures, arena); + let (lights, al_map) = scene.create_lights(&textures, &media, arena); let _have_scattering = { let shapes = scene.shapes.lock(); @@ -30,8 +30,7 @@ pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { .any(|sh| !sh.inside_medium.is_empty() || !sh.outside_medium.is_empty()) }; - let (aggregate, area_lights) = - scene.create_aggregate(&textures, &named_materials, &materials, &media, arena); + let aggregate = scene.create_aggregate(&textures, &named_materials, &materials, al_map, &media, arena); let mut all_lights = lights; all_lights.extend(area_lights); @@ -60,7 +59,7 @@ pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { loop { if let Some(isect) = aggregate.intersect(&ray, Some(Float::INFINITY)) { let intr = isect.intr; - if intr.material.is_null() { + if intr.material.is_none() { log::warn!("Ignoring material") } else { let world_from_render = camera.base().camera_transform.world_from_render; @@ -80,7 +79,7 @@ pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { log::debug!("Distance from camera: {}\n", intr.p().distance(cr.ray.o)); for (name, mtl) in &named_materials { - if *mtl == *intr.material.get().unwrap() { + if *mtl == intr.material { log::debug!("Named material: {}\n\n", name); break; } @@ -104,6 +103,7 @@ pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { sampler.clone(), aggregate.clone(), all_lights, + materials, arena, ); wf.render(); diff --git a/src/core/scene/scene.rs b/src/core/scene/scene.rs index 420edd7..bf342fa 100644 --- a/src/core/scene/scene.rs +++ b/src/core/scene/scene.rs @@ -32,6 +32,7 @@ use shared::core::primitive::{AnimatedPrimitive, GeometricPrimitive, Primitive, use shared::core::sampler::{Sampler, SamplerTrait}; use shared::core::shape::Shape; use shared::core::texture::SpectrumType; +use shared::core::{LightIdx, MaterialIdx}; use shared::spectra::RGBColorSpace; use shared::textures::FloatConstantTexture; use shared::utils::soa::SoA; @@ -77,28 +78,30 @@ fn resolve_medium_interface( fn resolve_material( mat_ref: &MaterialRef, - named_materials: &HashMap, + named_materials: &HashMap, materials: &[Material], loc: &FileLoc, - arena: &Arena, -) -> Material { + _arena: &Arena, +) -> MaterialIdx { match mat_ref { MaterialRef::Name(name) => match named_materials.get(name) { Some(m) => *m, None => { - log::error!("{}: named material '{}' not found", loc, name); - crate::core::material::default_diffuse_material(arena) + MaterialIdx::default() + // log::error!("{}: named material '{}' not found", loc, name); + // crate::core::material::default_diffuse_material(arena) } }, MaterialRef::Index(idx) => { if *idx < materials.len() { - materials[*idx] + MaterialIdx(*idx as u32) } else { - log::error!("{}: material index {} out of bounds", loc, idx); - crate::core::material::default_diffuse_material(arena) + MaterialIdx::default() + // log::error!("{}: material index {} out of bounds", loc, idx); + // crate::core::material::default_diffuse_material(arena) } } - MaterialRef::None => crate::core::material::default_diffuse_material(arena), + MaterialRef::None => MaterialIdx::default(), } } @@ -155,6 +158,8 @@ impl Default for BasicScene { } } +pub type AreaLightMap = HashMap<(usize, usize), LightIdx>; + impl BasicScene { pub fn new() -> Self { Self { @@ -295,26 +300,53 @@ impl BasicScene { arena: Arc, ) -> Result<()> { let mut state = self.texture_state.lock(); - self.add_texture_generic( - name, - texture, - &mut state, - |s| &mut s.serial_spectrum_textures, - |s| &mut s.spectrum_texture_jobs, - move |tex| { - let render_from_texture = tex.render_from_object.start_transform; - let tex_dict = TextureParameterDictionary::new(tex.base.parameters.into(), None); + + if texture.render_from_object.is_animated() { + log::info!( + "{}: animated world-to-texture not supported, using start transform", + texture.base.loc + ); + } + + if texture.base.name != "imagemap" && texture.base.name != "ptex" { + state.serial_spectrum_textures.push((name, texture)); + return Ok(()); + } + + let filename = resolve_filename(&texture.base.parameters.get_one_string("filename", "")?); + if !self.validate_texture_file(&filename, &texture.base.loc, &mut state.n_missing_textures) + { + return Ok(()); + } + + // Avoid duplicate work if the same file is already being loaded + if state.loading_texture_filenames.contains(&filename) { + state.serial_spectrum_textures.push((name, texture)); + return Ok(()); + } + + state.loading_texture_filenames.insert(filename.clone()); + state + .async_spectrum_textures + .push((name.clone(), texture.clone())); + + let job = run_async(move || { + let render_from_texture = texture.render_from_object.start_transform; + let tex_dict = TextureParameterDictionary::new(texture.base.parameters.into(), None); + Arc::new( SpectrumTexture::create( - &tex.base.name, + &texture.base.name, render_from_texture, tex_dict, SpectrumType::Albedo, - tex.base.loc, + texture.base.loc, &arena, ) - .expect("Could not create spectrum texture") - }, - ) + .expect("Could not create spectrum texture"), + ) + }); + state.spectrum_texture_jobs.insert(name, job); + Ok(()) } pub fn add_area_light(&self, light: SceneEntity) -> usize { @@ -371,6 +403,35 @@ impl BasicScene { unbounded_spectrum_textures: Arc::new(HashMap::new()), }; + for (name, entity) in state.async_spectrum_textures.drain(..) { + let render_from_texture = entity.render_from_object.start_transform; + let params = entity.base.parameters.clone(); + + let unbounded = SpectrumTexture::create( + &entity.base.name, + render_from_texture, + TextureParameterDictionary::new(params.clone().into(), None), + SpectrumType::Unbounded, + entity.base.loc.clone(), + arena, + ) + .expect("Could not create unbounded spectrum texture"); + + let illum = SpectrumTexture::create( + &entity.base.name, + render_from_texture, + TextureParameterDictionary::new(params.into(), None), + SpectrumType::Illuminant, + entity.base.loc, + arena, + ) + .expect("Could not create illuminant spectrum texture"); + + Arc::make_mut(&mut named.unbounded_spectrum_textures) + .insert(name.clone(), Arc::new(unbounded)); + Arc::make_mut(&mut named.illuminant_spectrum_textures).insert(name, Arc::new(illum)); + } + // Serial float textures may reference already-loaded textures for (name, entity) in state.serial_float_textures.drain(..) { let render_from_texture = entity.render_from_object.start_transform; @@ -408,6 +469,11 @@ impl BasicScene { let albedo = make(SpectrumType::Albedo, &named, entity.base.loc.clone()); let unbounded = make(SpectrumType::Unbounded, &named, entity.base.loc.clone()); let illum = make(SpectrumType::Illuminant, &named, entity.base.loc); + Arc::make_mut(&mut named.albedo_spectrum_textures) + .insert(name.clone(), Arc::new(albedo)); + Arc::make_mut(&mut named.unbounded_spectrum_textures) + .insert(name.clone(), Arc::new(unbounded)); + Arc::make_mut(&mut named.illuminant_spectrum_textures).insert(name, Arc::new(illum)); } named @@ -524,25 +590,31 @@ impl BasicScene { state.map.clone() } - pub fn create_lights(&self, _textures: &NamedTextures, arena: &Arena) -> Vec> { + pub fn create_lights( + &self, + textures: &NamedTextures, + media: &HashMap>, + arena: &Arena, + ) -> (Vec, AreaLightMap) { let light_state = self.light_state.lock(); + let shapes = self.shapes.lock(); + let film_cs = self.film_colorspace.lock(); + let film_cs_ref = film_cs.as_deref(); let camera = self .get_camera() .expect("Camera must be initialized before lights"); let camera_transform = camera.base().camera_transform; - let mut lights: Vec> = Vec::new(); - // Non-area lights created from stored entities + let mut lights: Vec = Vec::new(); + for entity in &light_state.lights { let medium = self.get_medium(&entity.medium, &entity.transformed_base.base.loc); - if entity.transformed_base.render_from_object.is_animated() { log::warn!( "{}: animated lights aren't supported, using start transform.", entity.transformed_base.base.loc ); } - match crate::core::light::create_light( &entity.transformed_base.base.name, entity.transformed_base.render_from_object.start_transform, @@ -552,28 +624,78 @@ impl BasicScene { camera_transform, arena, ) { - Ok(light) => lights.push(Arc::new(light)), - Err(e) => { - log::error!( - "{}: failed to create light: {}", - entity.transformed_base.base.loc, - e - ); + Ok(light) => lights.push(light), // bare Light, no Arc + Err(e) => log::error!( + "{}: failed to create light: {}", + entity.transformed_base.base.loc, + e + ), + } + } + + let mut area_map: AreaLightMap = HashMap::new(); + for (entity_idx, entity) in shapes.iter().enumerate() { + let Some(al_idx) = entity.light_index else { + continue; + }; + let al = &light_state.area_lights[al_idx]; + + let created_shapes = match Shape::create( + &entity.base.name, + *entity.render_from_object, + *entity.object_from_render, + entity.reverse_orientation, + entity.base.parameters.clone(), + &textures.float_textures, + entity.base.loc.clone(), + arena, + ) { + Ok(s) => s, + Err(_) => continue, + }; + + let alpha_tex = get_alpha_texture( + &entity.base.parameters, + &entity.base.loc, + &textures.float_textures, + ); + let cs = al.parameters.color_space.as_deref().or(film_cs_ref); + + for (sub_idx, shape) in created_shapes.iter().enumerate() { + let default_alpha = Arc::new(FloatTexture::default()); + let alpha_ref = alpha_tex.as_ref().unwrap_or(&default_alpha); + match crate::core::light::create_area_light( + *entity.render_from_object, + None, + &al.parameters, + &al.loc, + shape, + alpha_ref, + cs, + arena, + ) { + Ok(light) => { + let idx = LightIdx(lights.len() as u32); + lights.push(light); + area_map.insert((entity_idx, sub_idx), idx); + } + Err(e) => log::error!("{}: area light creation failed: {}", al.loc, e), } } } - lights + (lights, area_map) } pub fn create_aggregate( &self, textures: &NamedTextures, - named_materials: &HashMap, + named_materials: &HashMap, materials: &[Material], + area_map: &AreaLightMap, media: &HashMap>, arena: &Arena, - ) -> (Arc, Vec>) { + ) -> Arc { let mut shapes = self.shapes.lock(); let mut animated_shapes = self.animated_shapes.lock(); let mut instance_defs = self.instance_definitions.lock(); @@ -582,21 +704,16 @@ impl BasicScene { let film_cs = self.film_colorspace.lock(); let film_cs_ref = film_cs.as_deref(); - let mut all_lights: Vec> = Vec::new(); - log::info!("Starting shapes"); let mut primitives = Self::create_primitives_for_shapes( &shapes, textures, named_materials, materials, - &light_state, + area_map, media, - film_cs_ref, arena, - &mut all_lights, ); - shapes.clear(); shapes.shrink_to_fit(); @@ -605,72 +722,62 @@ impl BasicScene { textures, named_materials, materials, - &light_state, + area_map, media, - film_cs_ref, arena, - &mut all_lights, ); primitives.extend(animated_primitives); - animated_shapes.clear(); animated_shapes.shrink_to_fit(); log::info!("Finished shapes"); log::info!("Starting instances"); let mut resolved_defs: HashMap> = HashMap::new(); - for (name, def) in instance_defs.drain() { let mut inst_prims = Self::create_primitives_for_shapes( &def.shapes, textures, named_materials, materials, - &light_state, + area_map, media, - film_cs_ref, arena, - &mut all_lights, ); - let animated_inst_prims = Self::create_primitives_for_animated_shapes( &def.animated_shapes, textures, named_materials, materials, - &light_state, + area_map, media, - film_cs_ref, arena, - &mut all_lights, ); inst_prims.extend(animated_inst_prims); - let aggregate = if inst_prims.len() > 1 { - let bvh = BVHAggregate::new(inst_prims, 4, SplitMethod::SAH); - Some(Primitive::BVH(arena.alloc(bvh))) + Some(Primitive::BVH(arena.alloc(BVHAggregate::new( + inst_prims, + 4, + SplitMethod::SAH, + )))) } else if inst_prims.len() == 1 { Some(inst_prims.into_iter().next().unwrap()) } else { None }; - resolved_defs.insert(name, aggregate); } for inst in instances.drain(..) { let def = match resolved_defs.get(&inst.name) { Some(Some(prim)) => prim, - Some(None) => continue, // empty instance + Some(None) => continue, None => { log::error!("{}: object instance '{}' not defined", inst.loc, inst.name); continue; } }; - let prim = match &inst.transform { InstanceTransform::Static(xform) => { - // TransformedPrimitive wraps a primitive with a static transform Primitive::Transformed(shared::core::primitive::TransformedPrimitive { primitive: arena.alloc(*def), render_from_primitive: arena.alloc(**xform), @@ -683,7 +790,6 @@ impl BasicScene { }; primitives.push(prim); } - log::info!("Finished instances"); log::info!("Starting top-level accelerator"); @@ -695,7 +801,7 @@ impl BasicScene { let agg_ptr = arena.alloc(aggregate); log::info!("Finished top-level accelerator"); - (Arc::new(Primitive::BVH(agg_ptr)), all_lights) + Arc::new(Primitive::BVH(agg_ptr)) } // Integrator @@ -730,12 +836,21 @@ impl BasicScene { camera: Arc, sampler: Arc, aggregate: Arc, - lights: Vec>, + lights: Vec, + materials: Vec, arena: &Arena, ) -> CpuWavefrontRenderer { let integrator_entity = self.integrator.lock().clone().unwrap(); let params = &integrator_entity.parameters; - CpuWavefrontRenderer::create(params.clone(), camera, sampler, aggregate, lights, arena) + CpuWavefrontRenderer::create( + params.clone(), + camera, + sampler, + aggregate, + lights, + materials, + arena, + ) } // Getters @@ -776,17 +891,15 @@ impl BasicScene { fn create_primitives_for_shapes( shapes: &[ShapeSceneEntity], textures: &NamedTextures, - named_materials: &HashMap, + named_materials: &HashMap, materials: &[Material], - light_state: &LightState, + area_map: &AreaLightMap, media: &HashMap>, - film_cs: Option<&RGBColorSpace>, arena: &Arena, - area_lights: &mut Vec>, ) -> Vec { let mut primitives = Vec::new(); - for entity in shapes { + for (entity_idx, entity) in shapes.iter().enumerate() { let created_shapes = match Shape::create( &entity.base.name, *entity.render_from_object, @@ -808,22 +921,18 @@ impl BasicScene { continue; } - eprintln!("shape '{}' n={}", entity.base.name, created_shapes.len()); - - let mtl = resolve_material( + let mtl: MaterialIdx = resolve_material( &entity.material, named_materials, materials, &entity.base.loc, arena, ); - let alpha_tex = get_alpha_texture( &entity.base.parameters, &entity.base.loc, &textures.float_textures, ); - let mi = resolve_medium_interface( media, &entity.inside_medium, @@ -831,73 +940,39 @@ impl BasicScene { &entity.base.loc, ); - let al_entity = entity.light_index.map(|idx| &light_state.area_lights[idx]); + for (sub_idx, shape) in created_shapes.into_iter().enumerate() { + // look up the pre-created light index instead of creating one + let light_idx = area_map + .get(&(entity_idx, sub_idx)) + .copied() + .unwrap_or(LightIdx::NONE); - for shape in created_shapes { - // Create area light for this shape if the entity has one - let light_ptr = al_entity - .and_then(|al| { - let cs = al.parameters.color_space.as_deref().or(film_cs); - let default_alpha = Arc::new(FloatTexture::default()); - let alpha_ref = alpha_tex.as_ref().unwrap_or(&default_alpha); - match crate::core::light::create_area_light( - *entity.render_from_object, - None, - &al.parameters, - &al.loc, - &shape, - alpha_ref, - cs, - arena, - ) { - Ok(light) => { - area_lights.push(Arc::new(light)); - Some(arena.alloc(light)) - } - Err(e) => { - log::error!("{}: area light creation failed: {}", al.loc, e); - None - } - } - }) - .unwrap_or(Ptr::null()); - - // Pick SimplePrimitive when no extras are needed let prim = - if light_ptr.is_null() && !mi.is_medium_transition() && alpha_tex.is_none() { - Primitive::Simple(SimplePrimitive::new(shape, arena.alloc(mtl))) + if light_idx.is_none() && !mi.is_medium_transition() && alpha_tex.is_none() { + Primitive::Simple(SimplePrimitive::new(shape, mtl)) // mtl is MaterialIdx now } else { let alpha_ptr = alpha_tex .as_ref() .map(|t| arena.upload(t.as_ref())) .unwrap_or(Ptr::null()); - Primitive::Geometric(GeometricPrimitive::new( - shape, - arena.alloc(mtl), - light_ptr, - mi, - alpha_ptr, + shape, mtl, light_idx, mi, alpha_ptr, )) }; - primitives.push(prim); } } - primitives } fn create_primitives_for_animated_shapes( shapes: &[AnimatedShapeSceneEntity], textures: &NamedTextures, - named_materials: &HashMap, + named_materials: &HashMap, materials: &[Material], - light_state: &LightState, + area_map: &AreaLightMap, media: &HashMap>, - _film_cs: Option<&RGBColorSpace>, arena: &Arena, - _area_lights: &mut Vec>, ) -> Vec { let mut primitives = Vec::new(); @@ -950,9 +1025,7 @@ impl BasicScene { &entity.transformed_base.base.loc, ); - let al_entity = entity.light_index.map(|idx| &light_state.area_lights[idx]); - - if al_entity.is_some() { + if entity.light_index.is_some() { log::error!( "{}: animated area lights are not supported.", entity.transformed_base.base.loc @@ -963,7 +1036,7 @@ impl BasicScene { let mut base_prims = Vec::new(); for shape in created_shapes { let base = if !mi.is_medium_transition() && alpha_tex.is_none() { - Primitive::Simple(SimplePrimitive::new(shape, arena.alloc(mtl))) + Primitive::Simple(SimplePrimitive::new(shape, mtl)) } else { let alpha_ptr = alpha_tex .as_ref() @@ -972,8 +1045,8 @@ impl BasicScene { Primitive::Geometric(GeometricPrimitive::new( shape, - arena.alloc(mtl), - Ptr::null(), // no area light on animated shapes + mtl, + LightIdx::default(), // no area light on animated shapes mi, alpha_ptr, )) diff --git a/src/integrators/path.rs b/src/integrators/path.rs index a354cc4..8fe4a59 100644 --- a/src/integrators/path.rs +++ b/src/integrators/path.rs @@ -233,7 +233,7 @@ impl RayIntegratorTrait for PathIntegrator { if state.depth == 0 || state.specular_bounce { state.l += state.beta * le; } else if self.config.use_mis - && !isect.area_light.is_null() { + && !isect.area_light.is_none() { let light = &isect.area_light; let p_l = self.sampler.pmf_with_context(&state.prev_ctx, light) * light.pdf_li(&state.prev_ctx, ray.d, true); diff --git a/src/lights/sampler.rs b/src/lights/sampler.rs index 617d411..c6faa41 100644 --- a/src/lights/sampler.rs +++ b/src/lights/sampler.rs @@ -1,65 +1,40 @@ use crate::Arena; use shared::core::light::{Light, LightTrait}; -use shared::lights::sampler::{ - LightSampler, PowerLightSampler, UniformLightSampler, -}; -use shared::utils::sampling::AliasTable; +use shared::lights::sampler::{LightSampler, PowerLightSampler, UniformLightSampler}; use shared::spectra::{SampledSpectrum, SampledWavelengths}; +use shared::utils::sampling::AliasTable; use shared::utils::Ptr; use shared::Float; -use std::sync::Arc; -pub fn create_light_sampler( - name: &str, - lights: &[Arc], - arena: &Arena, -) -> LightSampler { - let device_lights = lights_to_slice(lights, arena); +pub fn create_light_sampler(name: &str, lights: &[Light], arena: &Arena) -> LightSampler { match name { - "uniform" => LightSampler::Uniform(create_uniform(device_lights, lights.len())), - "power" => LightSampler::Power(create_power(lights, device_lights, arena)), + "uniform" => LightSampler::Uniform(create_uniform(lights.len() as u32)), + "power" => LightSampler::Power(create_power(lights, arena)), "bvh" => { log::warn!("BVH light sampler not yet implemented, falling back to power"); - LightSampler::Power(create_power(lights, device_lights, arena)) + LightSampler::Power(create_power(lights, arena)) } _ => { log::error!("Unknown light sampler \"{}\", using power", name); - LightSampler::Power(create_power(lights, device_lights, arena)) + LightSampler::Power(create_power(lights, arena)) } } } -fn lights_to_slice(lights: &[Arc], arena: &Arena) -> (Ptr, u32) { +fn create_uniform(lights_len: u32) -> UniformLightSampler { + UniformLightSampler::new(lights_len) +} + +fn create_power(lights: &[Light], arena: &Arena) -> PowerLightSampler { if lights.is_empty() { - return (Ptr::null(), 0); - } - let vals: Vec = lights.iter().map(|l| **l).collect(); - let (ptr, _) = arena.alloc_slice(&vals); - (ptr, lights.len() as u32) -} - -fn create_uniform( - (lights, lights_len): (Ptr, u32), - _count: usize, -) -> UniformLightSampler { - UniformLightSampler::new(lights, lights_len) -} - -fn create_power( - host_lights: &[Arc], - (lights, lights_len): (Ptr, u32), - arena: &Arena, -) -> PowerLightSampler { - if host_lights.is_empty() { return PowerLightSampler { - lights: Ptr::null(), lights_len: 0, alias_table: Ptr::null(), }; } let lambda = SampledWavelengths::sample_visible(0.5); - let mut light_power: Vec = host_lights + let mut light_power: Vec = lights .iter() .map(|l| { let phi = SampledSpectrum::safe_div(&l.phi(lambda), &lambda.pdf()); @@ -67,7 +42,7 @@ fn create_power( }) .collect(); - // If all lights have zero power, treat as uniform + // If all lights have zero power, treat as uniform. if light_power.iter().sum::() == 0.0 { light_power.fill(1.0); } @@ -76,8 +51,7 @@ fn create_power( let alias_ptr = arena.alloc(alias_table); PowerLightSampler { - lights, - lights_len, + lights_len: lights.len() as u32, alias_table: alias_ptr, } } diff --git a/src/textures/image.rs b/src/textures/image.rs index 6448437..45494b4 100644 --- a/src/textures/image.rs +++ b/src/textures/image.rs @@ -3,14 +3,14 @@ use crate::core::texture::{ CreateFloatTexture, CreateSpectrumTexture, FloatTexture, FloatTextureTrait, SpectrumTexture, SpectrumTextureTrait, }; -use crate::utils::mipmap::{FilterFunction, MIPMap, MIPMapFilterOptions}; +use crate::utils::mipmap::{MIPMap, MIPMapFilterOptions}; use crate::utils::{resolve_filename, FileLoc, TextureParameterDictionary}; use crate::Arena; use anyhow::Result; use shared::core::color::RGB; use shared::core::color::{ColorEncoding, SRGBEncoding}; use shared::core::geometry::Vector2f; -use shared::core::image::WrapMode; +use shared::core::image::{FilterFunction, WrapMode}; use shared::core::spectrum::SpectrumTrait; use shared::core::texture::{SpectrumType, TexCoord2D, TextureEvalContext, TextureMapping2D}; use shared::spectra::{ diff --git a/src/utils/mipmap.rs b/src/utils/mipmap.rs index 8487945..207db58 100644 --- a/src/utils/mipmap.rs +++ b/src/utils/mipmap.rs @@ -1,7 +1,7 @@ use crate::core::image::{HostImage, ImageIO}; use shared::core::color::{ColorEncoding, RGB}; use shared::core::geometry::{Point2f, Point2i, Vector2f, VectorLike}; -use shared::core::image::{WrapMode, WrapMode2D}; +use shared::core::image::{WrapMode, WrapMode2D, FilterFunction}; use shared::spectra::RGBColorSpace; use anyhow::{bail, Result}; use shared::utils::math::{lerp, safe_sqrt, square}; @@ -13,39 +13,17 @@ use std::path::Path; #[cfg(feature = "cuda")] use std::sync::OnceLock; -#[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum FilterFunction { - Point, - Bilinear, - Trilinear, - Ewa, -} - -impl FilterFunction { - pub fn parse(name: &str) -> Result { - match name { - "ewa" | "EWA" => Ok(FilterFunction::Ewa), - "trilinear" => Ok(FilterFunction::Trilinear), - "bilinear" => Ok(FilterFunction::Bilinear), - "point" => Ok(FilterFunction::Point), - _ => bail!("Filter function unknown") - } - } -} - - -impl std::fmt::Display for FilterFunction { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let s = match self { - FilterFunction::Ewa => "EWA", - FilterFunction::Trilinear => "trilinear", - FilterFunction::Bilinear => "bilinear", - FilterFunction::Point => "point", - }; - write!(f, "{}", s) - } -} +// impl std::fmt::Display for FilterFunction { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// let s = match self { +// FilterFunction::Ewa => "EWA", +// FilterFunction::Trilinear => "trilinear", +// FilterFunction::Bilinear => "bilinear", +// FilterFunction::Point => "point", +// }; +// write!(f, "{}", s) +// } +// } #[repr(C)] #[derive(Debug, Clone, Copy)] diff --git a/src/wavefront/aggregate.rs b/src/wavefront/aggregate.rs index 3c0ac01..4e20f7e 100644 --- a/src/wavefront/aggregate.rs +++ b/src/wavefront/aggregate.rs @@ -3,21 +3,22 @@ use log::debug; use rayon::prelude::*; use shared::core::geometry::{Bounds3f, Ray, VectorLike}; use shared::core::interaction::{InteractionTrait, SurfaceInteraction}; -use shared::core::material::MaterialTrait; +use shared::core::material::{Material, MaterialTrait}; use shared::core::primitive::{Primitive, PrimitiveTrait}; use shared::core::texture::BasicTextureEvaluator; use shared::core::texture::TextureEvaluator; use shared::wavefront::workitems::*; use shared::wavefront::WavefrontAggregate; -use shared::{Float, Ptr}; +use shared::{Float, Ptr, GVec, gvec_from_slice}; pub struct CpuAggregate { pub aggregate: Primitive, + pub materials: GVec, } impl CpuAggregate { - pub fn new(aggregate: Primitive) -> Self { - Self { aggregate } + pub fn new(aggregate: Primitive, materials: &[Material]) -> Self { + Self { aggregate, materials: gvec_from_slice(materials) } } } @@ -70,7 +71,7 @@ impl WavefrontAggregate for CpuAggregate { let intr = &si.intr; // Medium transition - if intr.material.is_null() { + if intr.material.is_none() { let mut next = r; next.ray = intr.spawn_ray(r.ray.d); next_ray_q.push(next); @@ -78,7 +79,7 @@ impl WavefrontAggregate for CpuAggregate { } // Area light hit - if !intr.area_light.is_null() { + if !intr.area_light.is_none() { hit_area_light_q.push(HitAreaLightWorkItem { area_light: intr.area_light, p: intr.p(), @@ -97,7 +98,7 @@ impl WavefrontAggregate for CpuAggregate { } // Material eval queue dispatch - let material = *intr.material.get().unwrap(); + let material = &self.materials[intr.material.0 as usize]; let eval_q = if material.can_evaluate_textures(&BasicTextureEvaluator) { basic_eval_mtl_q } else { diff --git a/src/wavefront/integrator.rs b/src/wavefront/integrator.rs index cc2f90b..9c09ca5 100644 --- a/src/wavefront/integrator.rs +++ b/src/wavefront/integrator.rs @@ -16,11 +16,11 @@ use shared::core::geometry::{ }; use shared::core::interaction::InteractionTrait; use shared::core::light::{Light, LightSampleContext, LightTrait}; -use shared::core::material::{MaterialEvalContext, MaterialTrait}; -use shared::core::primitive::Primitive; -use shared::core::primitive::PrimitiveTrait; +use shared::core::material::{Material, MaterialEvalContext, MaterialTrait}; +use shared::core::primitive::{Primitive, PrimitiveTrait}; use shared::core::sampler::{get_camera_sample, CameraSample, Sampler, SamplerTrait}; use shared::core::texture::{BasicTextureEvaluator, TextureEvalContext, UniversalTextureEvaluator}; +use shared::core::LightIdx; use shared::lights::sampler::{LightSampler, LightSamplerTrait}; use shared::spectra::{SampledSpectrum, SampledWavelengths}; use shared::utils::math::square; @@ -28,7 +28,7 @@ use shared::utils::sampling::power_heuristic; use shared::utils::soa::{SoA, SoAAllocator, WorkQueue}; use shared::wavefront::workitems::*; use shared::wavefront::{WavefrontAggregate, WavefrontPathIntegrator, WavefrontRenderer}; -use shared::{gvec, Ptr, SHADOW_EPSILON}; +use shared::{gvec, gvec_from_slice, GVec, Ptr, SHADOW_EPSILON}; use std::ops::{Deref, DerefMut}; use std::sync::Arc; @@ -56,7 +56,8 @@ where camera: Arc, sampler: Arc, aggregate: Arc, - mut lights: Vec>, + mut lights: Vec, + materials: Vec, arena: &Arena, ) -> CpuWavefrontRenderer { let max_depth = parameters @@ -76,10 +77,10 @@ where let scanlines_per_pass = (max_samples / res_x).max(1); let max_queue_size = res_x * scanlines_per_pass; - let mut infinite_lights = gvec(); - for light in &lights { + let mut infinite_lights: GVec = gvec(); + for (i, light) in lights.iter().enumerate() { if light.light_type().is_infinite() { - infinite_lights.push(arena.alloc(**light)); + infinite_lights.push(LightIdx(i as u32)); } } @@ -87,9 +88,12 @@ where let bounds = aggregate.bounds(); for light in &mut lights { - Arc::::make_mut(light).preprocess(&bounds); + light.preprocess(&bounds); } + let lights: GVec = gvec_from_slice(&lights); + let materials: GVec = gvec_from_slice(&materials); + CpuWavefrontRenderer(WavefrontPathIntegrator { aggregate: cpu_aggregate, camera: (*camera).clone(), @@ -100,6 +104,8 @@ where samples_per_pixel: spp, regularize, infinite_lights, + lights, + materials, max_queue_size, scanlines_per_pass, light_sampler, @@ -304,8 +310,8 @@ impl CpuWavefrontRenderer { let mut l_contrib = SampledSpectrum::new(0.0); - for light_ptr in infinite_lights { - let light = light_ptr.get().unwrap(); + for idx in infinite_lights { + let light = &self.lights[idx.0 as usize]; let ray = Ray::new(w.ray_o, w.ray_d, None, Ptr::null()); let le = light.le(&ray, &w.lambda); if le.is_black() { @@ -317,7 +323,7 @@ impl CpuWavefrontRenderer { } else { // Compute MIS-weighted radiance contribution from infinite light let ctx = w.prev_intr_ctx; - let light_choice_pdf = light_sampler.pmf_with_context(&ctx, light); + let light_choice_pdf = light_sampler.pmf_with_context(&ctx, *idx); let r_l = w.r_l * light_choice_pdf * light.pdf_li(&ctx, w.ray_d, true); l_contrib += w.beta * le / (w.r_u + r_l).average(); } @@ -339,9 +345,12 @@ impl CpuWavefrontRenderer { let hit_area_light_queue = &self.hit_area_light_queue; (0..n as usize).into_par_iter().for_each(|i| { - let w = unsafe { hit_area_light_queue.storage.get(i) }; + let w = unsafe { hit_area_light_queue.get(i) }; + if w.area_light.is_none() { + return; + } + let light = &self.lights[w.area_light.0 as usize]; - let light = w.area_light.get().unwrap(); let le = light.l(w.p, w.n, w.uv, w.wo, &w.lambda); if le.is_black() { return; @@ -352,7 +361,7 @@ impl CpuWavefrontRenderer { } else { let wi = -w.wo; let ctx = w.prev_intr_ctx; - let light_choice_pdf = light_sampler.pmf_with_context(&ctx, light); + let light_choice_pdf = light_sampler.pmf_with_context(&ctx, w.area_light); // wi from previous interaction to this light hit let light_pdf = light_choice_pdf * light.pdf_li(&ctx, wi, true); let r_u = w.r_u; @@ -395,13 +404,13 @@ impl CpuWavefrontRenderer { (0..n as usize).into_par_iter().for_each(|i| { let w = unsafe { queue.storage.get(i) }; + if w.material.is_none() { + return; + } + let material = &self.materials[w.material.0 as usize]; + let pi = w.pixel_index as usize; let rs = pixel_sample_state.samples.get(pi); - - let Some(material) = w.material.get() else { - return; - }; - let _is_cond = material.is_conductor(); // GetMaterialEvalContext @@ -425,33 +434,6 @@ impl CpuWavefrontRenderer { let lambda = w.lambda; - // DIAGNOSTIC: print image texture evaluate result for first few diffuse hits - { - use std::sync::atomic::{AtomicU32, Ordering}; - static DIAG_COUNT: AtomicU32 = AtomicU32::new(0); - if let shared::core::material::Material::Diffuse(dm) = material { - if !dm.reflectance.is_null() { - let cnt = DIAG_COUNT.fetch_add(1, Ordering::Relaxed); - if cnt < 5 { - let ref_tex = dm.reflectance.get().unwrap(); - let val = ref_tex.evaluate(&ctx.texture, &lambda); - eprintln!( - "DIAG[{}] diffuse reflectance uv={:?} result={:?}", - cnt, w.uv, val - ); - if let shared::core::texture::SpectrumTexture::Image(img_tex) = ref_tex - { - eprintln!( - " image_ptr_null={} scale={}", - img_tex.image.is_null(), - img_tex.scale - ); - } - } - } - } - } - let mut bsdf = if use_universal { material.get_bsdf(&UniversalTextureEvaluator, &ctx, &lambda) } else { @@ -549,12 +531,9 @@ impl CpuWavefrontRenderer { else { return; }; + let light = &self.lights[sampled_light.light.0 as usize]; - let Some(ls) = - sampled_light - .light - .sample_li(&light_ctx, rs.direct.u, &lambda, true) - else { + let Some(ls) = light.sample_li(&light_ctx, rs.direct.u, &lambda, true) else { return; }; @@ -573,7 +552,7 @@ impl CpuWavefrontRenderer { let beta = w.beta * f * wi.abs_dot(ns.into()); let light_pdf = ls.pdf * sampled_light.p; - let bsdf_pdf = if sampled_light.light.light_type().is_delta_light() { + let bsdf_pdf = if light.light_type().is_delta_light() { 0.0 } else { bsdf.pdf(wo, wi, FArgs::default())